From db867c66a3508591211871cd1341929885df2a8c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Apr 2019 13:32:21 +1100 Subject: [PATCH 001/218] The ImageCropperPropertyValueEditor doesn't convert values in ConvertDbToString correctly --- .../PropertyEditors/ImageCropperPropertyValueEditor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 7bea542521..e5d98804be 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -174,7 +174,12 @@ namespace Umbraco.Web.PropertyEditors // more magic here ;-( var configuration = dataTypeService.GetDataType(propertyType.DataTypeId).ConfigurationAs(); var crops = configuration?.Crops ?? Array.Empty(); - return "{src: '" + val + "', crops: " + crops + "}"; + + return JsonConvert.SerializeObject(new + { + src = val, + crops = crops + }); } } } From 6b23a8221d20a240c270d87dad8930580bff81e9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 15 Apr 2019 16:30:46 +0200 Subject: [PATCH 002/218] Bugfix Audit service --- .../Persistence/Mappers/AuditItemMapper.cs | 3 ++- .../Querying/ModelToSqlExpressionVisitor.cs | 11 +++++++++++ .../Persistence/Repositories/IAuditRepository.cs | 2 ++ .../Repositories/Implement/AuditRepository.cs | 12 ++++++++++++ .../Services/Implement/AuditService.cs | 8 ++++---- src/Umbraco.Tests/Services/AuditServiceTests.cs | 15 +++++++++++++++ 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs index 853cd9f99e..48e7afdc7e 100644 --- a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs @@ -18,7 +18,8 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(AuditItem.Id), nameof(LogDto.NodeId)); DefineMap(nameof(AuditItem.CreateDate), nameof(LogDto.Datestamp)); DefineMap(nameof(AuditItem.UserId), nameof(LogDto.UserId)); - DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header)); + // we cannot map that one - because AuditType is an enum but Header is a string + //DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header)); DefineMap(nameof(AuditItem.Comment), nameof(LogDto.Comment)); } } diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs index 03d82a345f..c87937e41e 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs @@ -85,6 +85,17 @@ namespace Umbraco.Core.Persistence.Querying // I'm just unsure right now due to time constraints how to make it correct. It won't matter right now and has been working already with this bug but I've // only just discovered what it is actually doing. + // TODO + // in most cases we want to convert the value to a plain object, + // but for in some rare cases, we may want to do it differently, + // for instance a Models.AuditType (an enum) may in some cases + // need to be converted to its string value. + // but - we cannot have specific code here, really - and how would + // we configure this? is it even possible? + /* + var toString = typeof(object).GetMethod("ToString"); + var member = Expression.Call(m, toString); + */ var member = Expression.Convert(m, typeof(object)); var lambda = Expression.Lambda>(member); var getter = lambda.Compile(); diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs index 7c8a82bb85..b2dd6a3297 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs @@ -33,5 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories Direction orderDirection, AuditType[] auditTypeFilter, IQuery customFilter); + + IEnumerable Get(AuditType type, IQuery query); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index cda89fd89a..c25328b10c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -74,6 +74,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); } + public IEnumerable Get(AuditType type, IQuery query) + { + var sqlClause = GetBaseQuery(false) + .Where(x => x.Header == type.ToString()); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + var dtos = Database.Fetch(sql); + + return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); + } + protected override Sql GetBaseQuery(bool isCount) { var sql = SqlContext.Sql(); diff --git a/src/Umbraco.Core/Services/Implement/AuditService.cs b/src/Umbraco.Core/Services/Implement/AuditService.cs index 46c851a789..5eb08f2dea 100644 --- a/src/Umbraco.Core/Services/Implement/AuditService.cs +++ b/src/Umbraco.Core/Services/Implement/AuditService.cs @@ -51,8 +51,8 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { var result = sinceDate.HasValue == false - ? _auditRepository.Get(Query().Where(x => x.UserId == userId && x.AuditType == type)) - : _auditRepository.Get(Query().Where(x => x.UserId == userId && x.AuditType == type && x.CreateDate >= sinceDate.Value)); + ? _auditRepository.Get(type, Query().Where(x => x.UserId == userId)) + : _auditRepository.Get(type, Query().Where(x => x.UserId == userId && x.CreateDate >= sinceDate.Value)); scope.Complete(); return result; } @@ -63,8 +63,8 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { var result = sinceDate.HasValue == false - ? _auditRepository.Get(Query().Where(x => x.AuditType == type)) - : _auditRepository.Get(Query().Where(x => x.AuditType == type && x.CreateDate >= sinceDate.Value)); + ? _auditRepository.Get(type, Query()) + : _auditRepository.Get(type, Query().Where(x => x.CreateDate >= sinceDate.Value)); scope.Complete(); return result; } diff --git a/src/Umbraco.Tests/Services/AuditServiceTests.cs b/src/Umbraco.Tests/Services/AuditServiceTests.cs index 6064fe4acc..bfec246e61 100644 --- a/src/Umbraco.Tests/Services/AuditServiceTests.cs +++ b/src/Umbraco.Tests/Services/AuditServiceTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Umbraco.Core.Services.Implement; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Core.Models; namespace Umbraco.Tests.Services { @@ -48,5 +49,19 @@ namespace Umbraco.Tests.Services Assert.AreEqual(123 + 5, entries[0].PerformingUserId); Assert.AreEqual(123 + 4, entries[1].PerformingUserId); } + + [Test] + public void CanReadEntries() + { + var yesterday = DateTime.UtcNow.AddDays(-1); + + for (var i = 0; i < 10; i++) + { + yesterday = yesterday.AddMinutes(1); + ServiceContext.AuditService.Add(AuditType.Unpublish, -1, 33, "", "blah"); + } + + var logs = ServiceContext.AuditService.GetUserLogs(-1, AuditType.Unpublish); + } } } From 34ad8dfb8d726d6d9bb2b3e2791dcfdb231357b4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 15 Apr 2019 13:04:14 +0200 Subject: [PATCH 003/218] Introduce IPublishedContentType --- .../ContentVariationExtensions.cs | 8 +-- .../PublishedContent/IPublishedContentType.cs | 63 +++++++++++++++++++ .../IPublishedContentTypeFactory.cs | 6 +- .../PublishedContent/IPublishedElement.cs | 2 +- .../PublishedContent/PublishedContentType.cs | 45 ++++--------- .../PublishedContentTypeFactory.cs | 10 +-- .../PublishedContentWrapped.cs | 2 +- .../PublishedElementWrapped.cs | 2 +- .../PublishedContent/PublishedPropertyType.cs | 6 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../DictionaryPublishedContent.cs | 4 +- .../PublishedContentCache.cs | 4 +- .../PublishedMediaCache.cs | 6 +- .../PublishedMemberCache.cs | 4 +- .../XmlPublishedContent.cs | 8 +-- .../Published/NestedContentTests.cs | 6 +- .../PublishedContentDataTableTests.cs | 2 +- .../PublishedContent/PublishedContentTests.cs | 2 +- .../SolidPublishedSnapshot.cs | 10 +-- .../TestHelpers/Stubs/TestPublishedContent.cs | 2 +- .../PublishedContentHashtableConverter.cs | 2 +- .../Models/PublishedContentBase.cs | 2 +- .../PublishedCache/IPublishedCache.cs | 6 +- .../PublishedCache/IPublishedMemberCache.cs | 4 +- .../PublishedCache/NuCache/ContentCache.cs | 4 +- .../PublishedCache/NuCache/ContentNode.cs | 10 +-- .../PublishedCache/NuCache/ContentNodeKit.cs | 2 +- .../PublishedCache/NuCache/ContentStore.cs | 30 ++++----- .../PublishedCache/NuCache/MediaCache.cs | 4 +- .../PublishedCache/NuCache/MemberCache.cs | 4 +- .../NuCache/Navigable/NavigableContentType.cs | 8 +-- .../NuCache/PublishedContent.cs | 2 +- .../PublishedCache/NuCache/PublishedMember.cs | 6 +- .../NuCache/PublishedSnapshotService.cs | 6 +- .../PublishedCache/PublishedCacheBase.cs | 6 +- .../PublishedContentTypeCache.cs | 22 +++---- .../PublishedCache/PublishedElement.cs | 6 +- .../PublishedCache/PublishedMember.cs | 6 +- 38 files changed, 183 insertions(+), 140 deletions(-) create mode 100644 src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index d25997b5f0..c5e4a67fe2 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -66,24 +66,24 @@ namespace Umbraco.Core /// /// Determines whether the content type is invariant. /// - public static bool VariesByNothing(this PublishedContentType contentType) => contentType.Variations.VariesByNothing(); + public static bool VariesByNothing(this IPublishedContentType contentType) => contentType.Variations.VariesByNothing(); /// /// Determines whether the content type varies by culture. /// /// And then it could also vary by segment. - public static bool VariesByCulture(this PublishedContentType contentType) => contentType.Variations.VariesByCulture(); + public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture(); /// /// Determines whether the content type varies by segment. /// /// And then it could also vary by culture. - public static bool VariesBySegment(this PublishedContentType contentType) => contentType.Variations.VariesBySegment(); + public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment(); /// /// Determines whether the content type varies by culture and segment. /// - public static bool VariesByCultureAndSegment(this PublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); /// /// Determines whether the property type is invariant. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs new file mode 100644 index 0000000000..3c28ca1508 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents an type. + /// + /// Instances implementing the interface should be + /// immutable, ie if the content type changes, then a new instance needs to be created. + public interface IPublishedContentType + { + /// + /// Gets the content type identifier. + /// + int Id { get; } + + /// + /// Gets the content type alias. + /// + string Alias { get; } + + /// + /// Gets the content item type. + /// + PublishedItemType ItemType { get; } + + /// + /// Gets the aliases of the content types participating in the composition. + /// + HashSet CompositionAliases { get; } + + /// + /// Gets the content variations of the content type. + /// + ContentVariation Variations { get; } + + /// + /// Gets a value indicating whether this content type is for an element. + /// + bool IsElement { get; } + + /// + /// Gets the content type properties. + /// + IEnumerable PropertyTypes { get; } + + /// + /// Gets a property type index. + /// + /// The alias is case-insensitive. This is the only place where alias strings are compared. + int GetPropertyIndex(string alias); + + /// + /// Gets a property type. + /// + PublishedPropertyType GetPropertyType(string alias); + + /// + /// Gets a property type. + /// + PublishedPropertyType GetPropertyType(int index); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs index e75e8a4eb9..a43f572a74 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -10,7 +10,7 @@ /// /// An content type. /// A published content type corresponding to the item type and content type. - PublishedContentType CreateContentType(IContentTypeComposition contentType); + IPublishedContentType CreateContentType(IContentTypeComposition contentType); /// /// Creates a published property type. @@ -18,7 +18,7 @@ /// The published content type owning the property. /// A property type. /// Is used by constructor to create property types. - PublishedPropertyType CreatePropertyType(PublishedContentType contentType, PropertyType propertyType); + PublishedPropertyType CreatePropertyType(IPublishedContentType contentType, PropertyType propertyType); /// /// Creates a published property type. @@ -28,7 +28,7 @@ /// The datatype identifier. /// The variations. /// Is used by constructor to create special property types. - PublishedPropertyType CreatePropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); + PublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); /// /// Gets a published datatype. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs index 4b579d824b..4c72dc914a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the content type. /// - PublishedContentType ContentType { get; } + IPublishedContentType ContentType { get; } #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 0798e9a4e0..c6639b4109 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -5,11 +5,11 @@ using System.Linq; namespace Umbraco.Core.Models.PublishedContent { /// - /// Represents an type. + /// Represents an type. /// /// Instances of the class are immutable, ie /// if the content type changes, then a new class needs to be created. - public class PublishedContentType + public class PublishedContentType : IPublishedContentType { private readonly PublishedPropertyType[] _propertyTypes; @@ -103,44 +103,29 @@ namespace Umbraco.Core.Models.PublishedContent #region Content type - /// - /// Gets the content type identifier. - /// + /// public int Id { get; } - /// - /// Gets the content type alias. - /// + /// public string Alias { get; } - /// - /// Gets the content item type. - /// + /// public PublishedItemType ItemType { get; } - /// - /// Gets the aliases of the content types participating in the composition. - /// + /// public HashSet CompositionAliases { get; } - /// - /// Gets the content variations of the content type. - /// + /// public ContentVariation Variations { get; } #endregion #region Properties - /// - /// Gets the content type properties. - /// + /// public IEnumerable PropertyTypes => _propertyTypes; - /// - /// Gets a property type index. - /// - /// The alias is case-insensitive. This is the only place where alias strings are compared. + /// public int GetPropertyIndex(string alias) { if (_indexes.TryGetValue(alias, out var index)) return index; // fastest @@ -150,9 +135,7 @@ namespace Umbraco.Core.Models.PublishedContent // virtual for unit tests // TODO: explain why - /// - /// Gets a property type. - /// + /// public virtual PublishedPropertyType GetPropertyType(string alias) { var index = GetPropertyIndex(alias); @@ -161,17 +144,13 @@ namespace Umbraco.Core.Models.PublishedContent // virtual for unit tests // TODO: explain why - /// - /// Gets a property type. - /// + /// public virtual PublishedPropertyType GetPropertyType(int index) { return index >= 0 && index < _propertyTypes.Length ? _propertyTypes[index] : null; } - /// - /// Gets a value indicating whether this content type is for an element. - /// + /// public bool IsElement { get; } #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 2ca3593b55..01f01743de 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -26,31 +26,31 @@ namespace Umbraco.Core.Models.PublishedContent } /// - public PublishedContentType CreateContentType(IContentTypeComposition contentType) + public IPublishedContentType CreateContentType(IContentTypeComposition contentType) { return new PublishedContentType(contentType, this); } // for tests - internal PublishedContentType CreateContentType(int id, string alias, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); } // for tests - internal PublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); } /// - public PublishedPropertyType CreatePropertyType(PublishedContentType contentType, PropertyType propertyType) + public PublishedPropertyType CreatePropertyType(IPublishedContentType contentType, PropertyType propertyType) { return new PublishedPropertyType(contentType, propertyType, _propertyValueConverters, _publishedModelFactory, this); } /// - public PublishedPropertyType CreatePropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) + public PublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) { return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 8bf8cec244..7f3f38f629 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Models.PublishedContent #region ContentType /// - public virtual PublishedContentType ContentType => _content.ContentType; + public virtual IPublishedContentType ContentType => _content.ContentType; #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs index 1989ac2caf..481b9bd5d2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.Models.PublishedContent public IPublishedElement Unwrap() => _content; /// - public PublishedContentType ContentType => _content.ContentType; + public IPublishedContentType ContentType => _content.ContentType; /// public Guid Key => _content.Key; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 68892fd79a..8dd5003582 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// The new published property type belongs to the published content type. /// - public PublishedPropertyType(PublishedContentType contentType, PropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + public PublishedPropertyType(IPublishedContentType contentType, PropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) { ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); @@ -45,7 +45,7 @@ namespace Umbraco.Core.Models.PublishedContent /// Values are assumed to be consisted and are not checked. /// The new published property type belongs to the published content type. /// - public PublishedPropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + public PublishedPropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) { ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); @@ -78,7 +78,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the published content type containing the property type. /// - public PublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor + public IPublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor /// /// Gets the data type. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 68091fb3a9..d6d0a1d9e5 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -221,6 +221,7 @@ + diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index d3cbf1f183..e472de85dd 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -194,7 +194,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _getProperty(this, alias); } - public override PublishedContentType ContentType => _contentType; + public override IPublishedContentType ContentType => _contentType; private readonly List _keysAdded = new List(); private int _id; @@ -215,7 +215,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache //private Guid _version; private int _level; private readonly ICollection _properties; - private readonly PublishedContentType _contentType; + private readonly IPublishedContentType _contentType; private void ValidateAndSetProperty(IReadOnlyDictionary valueDictionary, Action setProperty, params string[] potentialKeys) { diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 5fc0d628c9..d69799dfdf 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -536,12 +536,12 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public override PublishedContentType GetContentType(int id) + public override IPublishedContentType GetContentType(int id) { return _contentTypeCache.Get(PublishedItemType.Content, id); } - public override PublishedContentType GetContentType(string alias) + public override IPublishedContentType GetContentType(string alias) { return _contentTypeCache.Get(PublishedItemType.Content, alias); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs index 71490465d0..8cdab6b2ae 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs @@ -609,17 +609,17 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public override PublishedContentType GetContentType(int id) + public override IPublishedContentType GetContentType(int id) { return _contentTypeCache.Get(PublishedItemType.Media, id); } - public override PublishedContentType GetContentType(string alias) + public override IPublishedContentType GetContentType(string alias) { return _contentTypeCache.Get(PublishedItemType.Media, alias); } - public override IEnumerable GetByContentType(PublishedContentType contentType) + public override IEnumerable GetByContentType(IPublishedContentType contentType) { throw new NotSupportedException(); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs index c882488f20..c28575f83d 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs @@ -138,12 +138,12 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public PublishedContentType GetContentType(int id) + public IPublishedContentType GetContentType(int id) { return _contentTypeCache.Get(PublishedItemType.Member, id); } - public PublishedContentType GetContentType(string alias) + public IPublishedContentType GetContentType(string alias) { return _contentTypeCache.Get(PublishedItemType.Member, alias); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index e1819bf0be..43c47ec569 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -53,7 +53,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private IEnumerable _children = Enumerable.Empty(); private IPublishedContent _parent; - private PublishedContentType _contentType; + private IPublishedContentType _contentType; private Dictionary _properties; private int _id; @@ -254,7 +254,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } - public override PublishedContentType ContentType + public override IPublishedContentType ContentType { get { @@ -308,8 +308,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName, out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path, out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft, - out PublishedContentType contentType, out Dictionary properties, - Func getPublishedContentType) + out IPublishedContentType contentType, out Dictionary properties, + Func getPublishedContentType) { //initialize the out params with defaults: writerName = null; diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 8f3b9a1df9..35be4b0bf2 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -23,7 +23,7 @@ namespace Umbraco.Tests.Published [TestFixture] public class NestedContentTests { - private (PublishedContentType, PublishedContentType) CreateContentTypes() + private (IPublishedContentType, IPublishedContentType) CreateContentTypes() { Current.Reset(); @@ -250,7 +250,7 @@ namespace Umbraco.Tests.Published class TestPublishedContent : PublishedContentBase { - public TestPublishedContent(PublishedContentType contentType, Guid key, IEnumerable properties, IUmbracoContextAccessor umbracoContextAccessor): base(umbracoContextAccessor) + public TestPublishedContent(IPublishedContentType contentType, Guid key, IEnumerable properties, IUmbracoContextAccessor umbracoContextAccessor): base(umbracoContextAccessor) { ContentType = contentType; Key = key; @@ -266,7 +266,7 @@ namespace Umbraco.Tests.Published public override bool IsPublished(string culture = null) => true; public override IPublishedContent Parent { get; } public override IEnumerable Children { get; } - public override PublishedContentType ContentType { get; } + public override IPublishedContentType ContentType { get; } // ReSharper restore UnassignedGetOnlyAutoProperty // ReSharper disable UnassignedGetOnlyAutoProperty diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 283ed1edd9..74b9619845 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -243,7 +243,7 @@ namespace Umbraco.Tests.PublishedContent return property; } - public PublishedContentType ContentType { get; set; } + public IPublishedContentType ContentType { get; set; } } } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index de641a99a2..605fae7a1b 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -930,7 +930,7 @@ namespace Umbraco.Tests.PublishedContent class ImageWithLegendModel : PublishedElement { - public ImageWithLegendModel(PublishedContentType contentType, Guid fragmentKey, Dictionary values, bool previewing) + public ImageWithLegendModel(IPublishedContentType contentType, Guid fragmentKey, Dictionary values, bool previewing) : base(contentType, fragmentKey, values, previewing) { } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 86017be820..c14a8c1740 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -137,17 +137,17 @@ namespace Umbraco.Tests.PublishedContent return _content.Count > 0; } - public override PublishedContentType GetContentType(int id) + public override IPublishedContentType GetContentType(int id) { throw new NotImplementedException(); } - public override PublishedContentType GetContentType(string alias) + public override IPublishedContentType GetContentType(string alias) { throw new NotImplementedException(); } - public override IEnumerable GetByContentType(PublishedContentType contentType) + public override IEnumerable GetByContentType(IPublishedContentType contentType) { throw new NotImplementedException(); } @@ -157,7 +157,7 @@ namespace Umbraco.Tests.PublishedContent { #region Constructor - public SolidPublishedContent(PublishedContentType contentType) + public SolidPublishedContent(IPublishedContentType contentType) { // initialize boring stuff TemplateId = 0; @@ -211,7 +211,7 @@ namespace Umbraco.Tests.PublishedContent #region ContentType - public PublishedContentType ContentType { get; private set; } + public IPublishedContentType ContentType { get; private set; } #endregion diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index a9abe96232..206660b904 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -7,7 +7,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs { internal class TestPublishedContent : PublishedElement, IPublishedContent { - public TestPublishedContent(PublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) + public TestPublishedContent(IPublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) : base(contentType, key, values, previewing) { Id = id; diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 9b3bc62cbf..0ff2a41867 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -218,7 +218,7 @@ namespace Umbraco.Web.Macros Parent = new PagePublishedContent(_inner.ParentId); } - public PublishedContentType ContentType { get; } + public IPublishedContentType ContentType { get; } public int Id { get; } diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 39933b49be..d62b8c6665 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Models #region ContentType - public abstract PublishedContentType ContentType { get; } + public abstract IPublishedContentType ContentType { get; } #endregion diff --git a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs index ff459a2d9b..0a597a7138 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs @@ -199,7 +199,7 @@ namespace Umbraco.Web.PublishedCache /// /// The content type unique identifier. /// The content type, or null. - PublishedContentType GetContentType(int id); + IPublishedContentType GetContentType(int id); /// /// Gets a content type identified by its alias. @@ -207,13 +207,13 @@ namespace Umbraco.Web.PublishedCache /// The content type alias. /// The content type, or null. /// The alias is case-insensitive. - PublishedContentType GetContentType(string alias); + IPublishedContentType GetContentType(string alias); /// /// Gets contents of a given content type. /// /// The content type. /// The contents. - IEnumerable GetByContentType(PublishedContentType contentType); + IEnumerable GetByContentType(IPublishedContentType contentType); } } diff --git a/src/Umbraco.Web/PublishedCache/IPublishedMemberCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedMemberCache.cs index 53d37a8d31..0ea812db83 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedMemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedMemberCache.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.PublishedCache /// /// The content type unique identifier. /// The content type, or null. - PublishedContentType GetContentType(int id); + IPublishedContentType GetContentType(int id); /// /// Gets a content type identified by its alias. @@ -30,6 +30,6 @@ namespace Umbraco.Web.PublishedCache /// The content type alias. /// The content type, or null. /// The alias is case-insensitive. - PublishedContentType GetContentType(string alias); + IPublishedContentType GetContentType(string alias); } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 0e74ea919f..d070b959ed 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -376,12 +376,12 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content types - public override PublishedContentType GetContentType(int id) + public override IPublishedContentType GetContentType(int id) { return _snapshot.GetContentType(id); } - public override PublishedContentType GetContentType(string alias) + public override IPublishedContentType GetContentType(string alias) { return _snapshot.GetContentType(alias); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index db7aa0d5d1..f7eb2ca19e 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.PublishedCache.NuCache internal class ContentNode { // special ctor with no content data - for members - public ContentNode(int id, Guid uid, PublishedContentType contentType, + public ContentNode(int id, Guid uid, IPublishedContentType contentType, int level, string path, int sortOrder, int parentContentId, DateTime createDate, int creatorId) @@ -28,7 +28,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ChildContentIds = new List(); } - public ContentNode(int id, Guid uid, PublishedContentType contentType, + public ContentNode(int id, Guid uid, IPublishedContentType contentType, int level, string path, int sortOrder, int parentContentId, DateTime createDate, int creatorId, @@ -60,7 +60,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // two-phase ctor, phase 2 - public void SetContentTypeAndData(PublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) + public void SetContentTypeAndData(IPublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) { ContentType = contentType; @@ -109,7 +109,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // clone with new content type - public ContentNode(ContentNode origin, PublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) + public ContentNode(ContentNode origin, IPublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) { Id = origin.Id; Uid = origin.Uid; @@ -136,7 +136,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // keep this as small as possible public readonly int Id; public readonly Guid Uid; - public PublishedContentType ContentType; + public IPublishedContentType ContentType; public readonly int Level; public readonly string Path; public readonly int SortOrder; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs index 753ba5cc94..08557fe3db 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public static ContentNodeKit Null { get; } = new ContentNodeKit { ContentTypeId = -1 }; public void Build( - PublishedContentType contentType, + IPublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, bool canBePublished, diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 48c68ab9bf..5693bd3204 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -24,8 +24,8 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ConcurrentDictionary> _contentNodes; private readonly ConcurrentDictionary> _contentRootNodes; - private readonly ConcurrentDictionary> _contentTypesById; - private readonly ConcurrentDictionary> _contentTypesByAlias; + private readonly ConcurrentDictionary> _contentTypesById; + private readonly ConcurrentDictionary> _contentTypesByAlias; private readonly ConcurrentDictionary _xmap; private readonly ILogger _logger; @@ -61,8 +61,8 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentNodes = new ConcurrentDictionary>(); _contentRootNodes = new ConcurrentDictionary>(); - _contentTypesById = new ConcurrentDictionary>(); - _contentTypesByAlias = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); + _contentTypesById = new ConcurrentDictionary>(); + _contentTypesByAlias = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); _xmap = new ConcurrentDictionary(); _genObjs = new ConcurrentQueue(); @@ -249,7 +249,7 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content types - public void NewContentTypes(IEnumerable types) + public void NewContentTypes(IEnumerable types) { var lockInfo = new WriteLockInfo(); try @@ -268,7 +268,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public void UpdateContentTypes(IEnumerable types) + public void UpdateContentTypes(IEnumerable types) { var lockInfo = new WriteLockInfo(); try @@ -288,7 +288,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var node = link.Value; if (node == null) continue; var contentTypeId = node.ContentType.Id; - if (index.TryGetValue(contentTypeId, out PublishedContentType contentType) == false) continue; + if (index.TryGetValue(contentTypeId, out var contentType) == false) continue; SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor)); } } @@ -298,10 +298,10 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public void UpdateContentTypes(IEnumerable removedIds, IEnumerable refreshedTypes, IEnumerable kits) + public void UpdateContentTypes(IEnumerable removedIds, IEnumerable refreshedTypes, IEnumerable kits) { var removedIdsA = removedIds?.ToArray() ?? Array.Empty(); - var refreshedTypesA = refreshedTypes?.ToArray() ?? Array.Empty(); + var refreshedTypesA = refreshedTypes?.ToArray() ?? Array.Empty(); var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToArray(); kits = kits ?? Array.Empty(); @@ -377,7 +377,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public void UpdateDataTypes(IEnumerable dataTypeIds, Func getContentType) + public void UpdateDataTypes(IEnumerable dataTypeIds, Func getContentType) { var lockInfo = new WriteLockInfo(); try @@ -434,7 +434,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return false; // unknown = bad - if (_contentTypesById.TryGetValue(kit.ContentTypeId, out LinkedNode link) == false || link.Value == null) + if (_contentTypesById.TryGetValue(kit.ContentTypeId, out var link) == false || link.Value == null) return false; // check whether parent is published @@ -830,12 +830,12 @@ namespace Umbraco.Web.PublishedCache.NuCache return has == false; } - public PublishedContentType GetContentType(int id, long gen) + public IPublishedContentType GetContentType(int id, long gen) { return GetValue(_contentTypesById, id, gen); } - public PublishedContentType GetContentType(string alias, long gen) + public IPublishedContentType GetContentType(string alias, long gen) { return GetValue(_contentTypesByAlias, alias, gen); } @@ -1151,14 +1151,14 @@ namespace Umbraco.Web.PublishedCache.NuCache return _store.GetAll(_gen); } - public PublishedContentType GetContentType(int id) + public IPublishedContentType GetContentType(int id) { if (_gen < 0) throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); return _store.GetContentType(id, _gen); } - public PublishedContentType GetContentType(string alias) + public IPublishedContentType GetContentType(string alias) { if (_gen < 0) throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs index f7bdb4400f..d63035b219 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs @@ -156,12 +156,12 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content types - public override PublishedContentType GetContentType(int id) + public override IPublishedContentType GetContentType(int id) { return _snapshot.GetContentType(id); } - public override PublishedContentType GetContentType(string alias) + public override IPublishedContentType GetContentType(string alias) { return _snapshot.GetContentType(alias); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs index f7ffe73109..5164b2b3bf 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs @@ -151,12 +151,12 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content types - public PublishedContentType GetContentType(int id) + public IPublishedContentType GetContentType(int id) { return _contentTypeCache.Get(PublishedItemType.Member, id); } - public PublishedContentType GetContentType(string alias) + public IPublishedContentType GetContentType(string alias) { return _contentTypeCache.Get(PublishedItemType.Member, alias); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContentType.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContentType.cs index 18bf3ead13..310dae9dd2 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContentType.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContentType.cs @@ -22,10 +22,10 @@ namespace Umbraco.Web.PublishedCache.NuCache.Navigable // changes, but they are replaced by a new instance, so our map here will clean itself automatically and // we don't have to manage cache - ConditionalWeakTable does not prevent keys from being GCed - private static readonly ConditionalWeakTable TypesMap - = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable TypesMap + = new ConditionalWeakTable(); - public static NavigableContentType GetContentType(PublishedContentType contentType) + public static NavigableContentType GetContentType(IPublishedContentType contentType) { return TypesMap.GetOrCreateValue(contentType).EnsureInitialized(contentType); } @@ -49,7 +49,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.Navigable }; } - private NavigableContentType EnsureInitialized(PublishedContentType contentType) + private NavigableContentType EnsureInitialized(IPublishedContentType contentType) { lock (_locko) { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 057823f29c..5712d55973 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -166,7 +166,7 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content Type /// - public override PublishedContentType ContentType => _contentNode.ContentType; + public override IPublishedContentType ContentType => _contentNode.ContentType; #endregion diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs index 11ca169300..4bfcbb2a3d 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public static IPublishedContent Create( IMember member, - PublishedContentType contentType, + IPublishedContentType contentType, bool previewing, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, @@ -53,7 +53,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return new PublishedMember(member, n, d, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor).CreateModel(); } - private static Dictionary GetPropertyValues(PublishedContentType contentType, IMember member) + private static Dictionary GetPropertyValues(IPublishedContentType contentType, IMember member) { // see node in PublishedSnapshotService // we do not (want to) support ConvertDbToXml/String @@ -91,7 +91,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return properties; } - private static void AddIf(PublishedContentType contentType, IDictionary properties, string alias, object value) + private static void AddIf(IPublishedContentType contentType, IDictionary properties, string alias, object value) { var propertyType = contentType.GetPropertyType(alias); if (propertyType == null || propertyType.IsUserProperty) return; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index bf16074040..3b683cdd4e 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -898,11 +898,11 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content Types - private IEnumerable CreateContentTypes(PublishedItemType itemType, int[] ids) + private IEnumerable CreateContentTypes(PublishedItemType itemType, int[] ids) { // XxxTypeService.GetAll(empty) returns everything! if (ids.Length == 0) - return Enumerable.Empty(); + return Enumerable.Empty(); IEnumerable contentTypes; switch (itemType) @@ -925,7 +925,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x)); } - private PublishedContentType CreateContentType(PublishedItemType itemType, int id) + private IPublishedContentType CreateContentType(PublishedItemType itemType, int id) { IContentTypeComposition contentType; switch (itemType) diff --git a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs index b0fe1a4240..44f0499a63 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs @@ -88,11 +88,11 @@ namespace Umbraco.Web.PublishedCache return HasContent(PreviewDefault); } - public abstract PublishedContentType GetContentType(int id); + public abstract IPublishedContentType GetContentType(int id); - public abstract PublishedContentType GetContentType(string alias); + public abstract IPublishedContentType GetContentType(string alias); - public virtual IEnumerable GetByContentType(PublishedContentType contentType) + public virtual IEnumerable GetByContentType(IPublishedContentType contentType) { // this is probably not super-efficient, but works // some cache implementation may want to override it, though diff --git a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs index ca30370598..e453471bb8 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs @@ -15,8 +15,8 @@ namespace Umbraco.Web.PublishedCache /// This cache is not snapshotted, so it refreshes any time things change. public class PublishedContentTypeCache { - private readonly Dictionary _typesByAlias = new Dictionary(); - private readonly Dictionary _typesById = new Dictionary(); + private readonly Dictionary _typesByAlias = new Dictionary(); + private readonly Dictionary _typesById = new Dictionary(); private readonly IContentTypeService _contentTypeService; private readonly IMediaTypeService _mediaTypeService; private readonly IMemberTypeService _memberTypeService; @@ -136,7 +136,7 @@ namespace Umbraco.Web.PublishedCache /// An item type. /// An alias. /// The published content type corresponding to the item type and alias. - public PublishedContentType Get(PublishedItemType itemType, string alias) + public IPublishedContentType Get(PublishedItemType itemType, string alias) { var aliasKey = GetAliasKey(itemType, alias); @@ -174,7 +174,7 @@ namespace Umbraco.Web.PublishedCache /// An item type. /// An identifier. /// The published content type corresponding to the item type and identifier. - public PublishedContentType Get(PublishedItemType itemType, int id) + public IPublishedContentType Get(PublishedItemType itemType, int id) { try { @@ -204,7 +204,7 @@ namespace Umbraco.Web.PublishedCache } } - private PublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias) + private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias) { if (GetPublishedContentTypeByAlias != null) return GetPublishedContentTypeByAlias(alias); @@ -231,7 +231,7 @@ namespace Umbraco.Web.PublishedCache return _publishedContentTypeFactory.CreateContentType(contentType); } - private PublishedContentType CreatePublishedContentType(PublishedItemType itemType, int id) + private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, int id) { if (GetPublishedContentTypeById != null) return GetPublishedContentTypeById(id); @@ -259,8 +259,8 @@ namespace Umbraco.Web.PublishedCache } // for unit tests - changing the callback must reset the cache obviously - private Func _getPublishedContentTypeByAlias; - internal Func GetPublishedContentTypeByAlias + private Func _getPublishedContentTypeByAlias; + internal Func GetPublishedContentTypeByAlias { get => _getPublishedContentTypeByAlias; set @@ -282,8 +282,8 @@ namespace Umbraco.Web.PublishedCache } // for unit tests - changing the callback must reset the cache obviously - private Func _getPublishedContentTypeById; - internal Func GetPublishedContentTypeById + private Func _getPublishedContentTypeById; + internal Func GetPublishedContentTypeById { get => _getPublishedContentTypeById; set @@ -326,7 +326,7 @@ namespace Umbraco.Web.PublishedCache return k + ":" + alias; } - private static string GetAliasKey(PublishedContentType contentType) + private static string GetAliasKey(IPublishedContentType contentType) { return GetAliasKey(contentType.ItemType, contentType.Alias); } diff --git a/src/Umbraco.Web/PublishedCache/PublishedElement.cs b/src/Umbraco.Web/PublishedCache/PublishedElement.cs index 41902e3e26..618c075b9b 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedElement.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedElement.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PublishedCache { // initializes a new instance of the PublishedElement class // within the context of a published snapshot service (eg a published content property value) - public PublishedElement(PublishedContentType contentType, Guid key, Dictionary values, bool previewing, + public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary values, bool previewing, PropertyCacheLevel referenceCacheLevel, IPublishedSnapshotAccessor publishedSnapshotAccessor) { if (key == Guid.Empty) throw new ArgumentException("Empty guid."); @@ -46,7 +46,7 @@ namespace Umbraco.Web.PublishedCache // + using an initial reference cache level of .None ensures that everything will be // cached at .Content level - and that reference cache level will propagate to all // properties - public PublishedElement(PublishedContentType contentType, Guid key, Dictionary values, bool previewing) + public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary values, bool previewing) : this(contentType, key, values, previewing, PropertyCacheLevel.None, null) { } @@ -60,7 +60,7 @@ namespace Umbraco.Web.PublishedCache #region ContentType - public PublishedContentType ContentType { get; } + public IPublishedContentType ContentType { get; } #endregion diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 419c279d46..d954411f5e 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -17,11 +17,11 @@ namespace Umbraco.Web.PublishedCache private readonly IMember _member; private readonly IMembershipUser _membershipUser; private readonly IPublishedProperty[] _properties; - private readonly PublishedContentType _publishedMemberType; + private readonly IPublishedContentType _publishedMemberType; public PublishedMember( IMember member, - PublishedContentType publishedMemberType, + IPublishedContentType publishedMemberType, IUmbracoContextAccessor umbracoContextAccessor) :base(umbracoContextAccessor) { @@ -126,7 +126,7 @@ namespace Umbraco.Web.PublishedCache properties.Add(property); } - public override PublishedContentType ContentType => _publishedMemberType; + public override IPublishedContentType ContentType => _publishedMemberType; public override int Id => _member.Id; From f039b00a44a57b0d32e956852fa878ac59e4badc Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 15 Apr 2019 17:14:45 +0200 Subject: [PATCH 004/218] Introduce IPublishedPropertyType --- .../ContentVariationExtensions.cs | 8 +- .../PublishedContent/IPublishedContentType.cs | 6 +- .../IPublishedContentTypeFactory.cs | 4 +- .../PublishedContent/IPublishedProperty.cs | 2 +- .../IPublishedPropertyType.cs | 108 ++++++++++++++++++ .../PublishedContent/PublishedContentType.cs | 23 ++-- .../PublishedContentTypeFactory.cs | 17 ++- .../PublishedContent/PublishedPropertyBase.cs | 4 +- .../PublishedContent/PublishedPropertyType.cs | 88 +++----------- .../PublishedContent/RawValueProperty.cs | 2 +- .../IPropertyValueConverter.cs | 12 +- .../PropertyValueConverterBase.cs | 12 +- .../CheckboxListValueConverter.cs | 8 +- .../ColorPickerValueConverter.cs | 10 +- .../DatePickerValueConverter.cs | 10 +- .../ValueConverters/DecimalValueConverter.cs | 8 +- .../EmailAddressValueConverter.cs | 8 +- .../ValueConverters/GridValueConverter.cs | 8 +- .../ImageCropperValueConverter.cs | 8 +- .../ValueConverters/IntegerValueConverter.cs | 8 +- .../ValueConverters/JsonValueConverter.cs | 8 +- .../ValueConverters/LabelValueConverter.cs | 8 +- .../MarkdownEditorValueConverter.cs | 12 +- .../MemberGroupPickerValueConverter.cs | 8 +- .../MultipleTextStringValueConverter.cs | 10 +- .../MustBeStringValueConverter.cs | 8 +- .../RadioButtonListValueConverter.cs | 8 +- .../ValueConverters/SliderValueConverter.cs | 8 +- .../ValueConverters/TagsValueConverter.cs | 10 +- .../TextStringValueConverter.cs | 12 +- .../ValueConverters/TinyMceValueConverter.cs | 12 +- .../UploadPropertyConverter.cs | 8 +- .../ValueConverters/YesNoValueConverter.cs | 10 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../XmlPublishedProperty.cs | 6 +- .../Published/ConvertersTests.cs | 80 ++++++------- .../Published/NestedContentTests.cs | 27 +++-- .../Published/PropertyCacheLevelTests.cs | 39 ++++--- .../PublishedContentLanguageVariantTests.cs | 27 +++-- .../PublishedContentMoreTests.cs | 18 +-- .../PublishedContentTestBase.cs | 14 +-- .../PublishedContent/PublishedContentTests.cs | 41 ++++--- .../SolidPublishedSnapshot.cs | 14 ++- .../PublishedContentHashtableConverter.cs | 4 +- src/Umbraco.Web/Models/PublishedProperty.cs | 4 +- .../ContentPickerValueConverter.cs | 12 +- .../FlexibleDropdownPropertyValueConverter.cs | 8 +- .../MacroContainerValueConverter.cs | 8 +- .../MarkdownEditorValueConverter.cs | 10 +- .../MediaPickerValueConverter.cs | 10 +- .../MemberPickerValueConverter.cs | 10 +- .../MultiNodeTreePickerValueConverter.cs | 10 +- .../MultiUrlPickerValueConverter.cs | 10 +- .../NestedContentManyValueConverter.cs | 12 +- .../NestedContentSingleValueConverter.cs | 12 +- .../NestedContentValueConverterBase.cs | 6 +- .../RteMacroRenderingValueConverter.cs | 4 +- .../TextStringValueConverter.cs | 12 +- .../PublishedCache/NuCache/Property.cs | 4 +- .../PublishedElementPropertyBase.cs | 2 +- 60 files changed, 497 insertions(+), 384 deletions(-) create mode 100644 src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index c5e4a67fe2..9fdc5f0b90 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -88,22 +88,22 @@ namespace Umbraco.Core /// /// Determines whether the property type is invariant. /// - public static bool VariesByNothing(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); + public static bool VariesByNothing(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); /// /// Determines whether the property type varies by culture. /// - public static bool VariesByCulture(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); + public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); /// /// Determines whether the property type varies by segment. /// - public static bool VariesBySegment(this PublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); + public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); /// /// Determines whether the property type varies by culture and segment. /// - public static bool VariesByCultureAndSegment(this PublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); /// /// Determines whether a variation is invariant. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs index 3c28ca1508..ab6920377c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the content type properties. /// - IEnumerable PropertyTypes { get; } + IEnumerable PropertyTypes { get; } /// /// Gets a property type index. @@ -53,11 +53,11 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets a property type. /// - PublishedPropertyType GetPropertyType(string alias); + IPublishedPropertyType GetPropertyType(string alias); /// /// Gets a property type. /// - PublishedPropertyType GetPropertyType(int index); + IPublishedPropertyType GetPropertyType(int index); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs index a43f572a74..816bfdbb01 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -18,7 +18,7 @@ /// The published content type owning the property. /// A property type. /// Is used by constructor to create property types. - PublishedPropertyType CreatePropertyType(IPublishedContentType contentType, PropertyType propertyType); + IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, PropertyType propertyType); /// /// Creates a published property type. @@ -28,7 +28,7 @@ /// The datatype identifier. /// The variations. /// Is used by constructor to create special property types. - PublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); + IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); /// /// Gets a published datatype. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index 9a00e94d3e..2ee7dcb28f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -5,7 +5,7 @@ /// public interface IPublishedProperty { - PublishedPropertyType PropertyType { get; } + IPublishedPropertyType PropertyType { get; } /// /// Gets the alias of the property. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs new file mode 100644 index 0000000000..40f2bf3df2 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -0,0 +1,108 @@ +using System; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a published property type. + /// + /// Instances implementing the interface should be + /// immutable, ie if the property type changes, then a new instance needs to be created. + public interface IPublishedPropertyType + { + /// + /// Gets the published content type containing the property type. + /// + IPublishedContentType ContentType { get; } + + /// + /// Gets the data type. + /// + PublishedDataType DataType { get; } + + /// + /// Gets property type alias. + /// + string Alias { get; } + + /// + /// Gets the property editor alias. + /// + string EditorAlias { get; } + + /// + /// Gets a value indicating whether the property is a user content property. + /// + /// A non-user content property is a property that has been added to a + /// published content type by Umbraco but does not corresponds to a user-defined + /// published property. + bool IsUserProperty { get; } + + /// + /// Gets the content variations of the property type. + /// + ContentVariation Variations { get; } + + /// + /// Determines whether a value is an actual value, or not a value. + /// + /// Used by property.HasValue and, for instance, in fallback scenarios. + bool? IsValue(object value, PropertyValueLevel level); + + /// + /// Gets the property cache level. + /// + PropertyCacheLevel CacheLevel { get; } + + /// + /// Converts the source value into the intermediate value. + /// + /// The published element owning the property. + /// The source value. + /// A value indicating whether content should be considered draft. + /// The intermediate value. + object ConvertSourceToInter(IPublishedElement owner, object source, bool preview); + + /// + /// Converts the intermediate value into the object value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The object value. + object ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + + /// + /// Converts the intermediate value into the XPath value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The XPath value. + /// + /// The XPath value can be either a string or an XPathNavigator. + /// + object ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + + /// + /// Gets the property model CLR type. + /// + /// + /// The model CLR type may be a type, or may contain types. + /// For the actual CLR type, see . + /// + Type ModelClrType { get; } + + /// + /// Gets the property CLR type. + /// + /// + /// Returns the actual CLR type which does not contain types. + /// Mapping from may throw if some instances + /// could not be mapped to actual CLR types. + /// + Type ClrType { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index c6639b4109..fedd7445b7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models.PublishedContent /// if the content type changes, then a new class needs to be created. public class PublishedContentType : IPublishedContentType { - private readonly PublishedPropertyType[] _propertyTypes; + private readonly IPublishedPropertyType[] _propertyTypes; // fast alias-to-index xref containing both the raw alias and its lowercase version private readonly Dictionary _indexes = new Dictionary(); @@ -34,11 +34,11 @@ namespace Umbraco.Core.Models.PublishedContent InitializeIndexes(); } + // fixme should be internal? /// - /// Initializes a new instance of the with specific values. + /// This constructor is for tests and is not intended to be used directly from application code. /// /// - /// This constructor is for tests and is not intended to be used directly from application code. /// Values are assumed to be consisted and are not checked. /// public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) @@ -52,6 +52,15 @@ namespace Umbraco.Core.Models.PublishedContent InitializeIndexes(); } + // fixme + public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) + : this(id, alias, itemType, compositionAliases, variations, isElement) + { + _propertyTypes = propertyTypes(this).ToArray(); + + InitializeIndexes(); + } + private PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) { Id = id; @@ -75,7 +84,7 @@ namespace Umbraco.Core.Models.PublishedContent // Members have properties such as IMember LastLoginDate which are plain C# properties and not content // properties; they are exposed as pseudo content properties, as long as a content property with the // same alias does not exist already. - private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) + private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) { var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); @@ -123,7 +132,7 @@ namespace Umbraco.Core.Models.PublishedContent #region Properties /// - public IEnumerable PropertyTypes => _propertyTypes; + public IEnumerable PropertyTypes => _propertyTypes; /// public int GetPropertyIndex(string alias) @@ -136,7 +145,7 @@ namespace Umbraco.Core.Models.PublishedContent // virtual for unit tests // TODO: explain why /// - public virtual PublishedPropertyType GetPropertyType(string alias) + public virtual IPublishedPropertyType GetPropertyType(string alias) { var index = GetPropertyIndex(alias); return GetPropertyType(index); @@ -145,7 +154,7 @@ namespace Umbraco.Core.Models.PublishedContent // virtual for unit tests // TODO: explain why /// - public virtual PublishedPropertyType GetPropertyType(int index) + public virtual IPublishedPropertyType GetPropertyType(int index) { return index >= 0 && index < _propertyTypes.Length ? _propertyTypes[index] : null; } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 01f01743de..78d05607b4 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -31,32 +31,43 @@ namespace Umbraco.Core.Models.PublishedContent return new PublishedContentType(contentType, this); } + // fixme kill // for tests internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); } + internal IPublishedContentType CreateContentType(int id, string alias, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + { + return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); + } + // fixme kill // for tests internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); } + internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + { + return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); + } /// - public PublishedPropertyType CreatePropertyType(IPublishedContentType contentType, PropertyType propertyType) + public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, PropertyType propertyType) { return new PublishedPropertyType(contentType, propertyType, _propertyValueConverters, _publishedModelFactory, this); } /// - public PublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) + public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) { return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this); } + // fixme kill // for tests - internal PublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.Nothing) + internal IPublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.Nothing) { return new PublishedPropertyType(propertyTypeAlias, dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index 5f374f8bc8..e11d2391ec 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Initializes a new instance of the class. /// - protected PublishedPropertyBase(PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) + protected PublishedPropertyBase(IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) { PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); ReferenceCacheLevel = referenceCacheLevel; @@ -42,7 +42,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the property type. /// - public PublishedPropertyType PropertyType { get; } + public IPublishedPropertyType PropertyType { get; } /// /// Gets the property reference cache level. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 8dd5003582..1632a85f44 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -10,10 +10,8 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Instances of the class are immutable, ie /// if the property type changes, then a new class needs to be created. - public class PublishedPropertyType + public class PublishedPropertyType : IPublishedPropertyType { - // TODO: API design review, should this be an interface? - private readonly IPublishedModelFactory _publishedModelFactory; private readonly PropertyValueConverterCollection _propertyValueConverters; private readonly object _locker = new object(); @@ -38,6 +36,7 @@ namespace Umbraco.Core.Models.PublishedContent ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); } + // fixme should be internal? /// /// This constructor is for tests and is not intended to be used directly from application code. /// @@ -51,6 +50,7 @@ namespace Umbraco.Core.Models.PublishedContent ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); } + // fixme should be internal? /// /// This constructor is for tests and is not intended to be used directly from application code. /// @@ -75,37 +75,22 @@ namespace Umbraco.Core.Models.PublishedContent #region Property type - /// - /// Gets the published content type containing the property type. - /// + /// public IPublishedContentType ContentType { get; internal set; } // internally set by PublishedContentType constructor - /// - /// Gets the data type. - /// + /// public PublishedDataType DataType { get; } - /// - /// Gets property type alias. - /// + /// public string Alias { get; } - /// - /// Gets the property editor alias. - /// + /// public string EditorAlias => DataType.EditorAlias; - /// - /// Gets a value indicating whether the property is a user content property. - /// - /// A non-user content property is a property that has been added to a - /// published content type by Umbraco but does not corresponds to a user-defined - /// published property. + /// public bool IsUserProperty { get; } - /// - /// Gets the content variations of the property type. - /// + /// public ContentVariation Variations { get; } #endregion @@ -193,10 +178,7 @@ namespace Umbraco.Core.Models.PublishedContent _modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this); } - /// - /// Determines whether a value is an actual value, or not a value. - /// - /// Used by property.HasValue and, for instance, in fallback scenarios. + /// public bool? IsValue(object value, PropertyValueLevel level) { if (!_initialized) Initialize(); @@ -209,9 +191,7 @@ namespace Umbraco.Core.Models.PublishedContent return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); } - /// - /// Gets the property cache level. - /// + /// public PropertyCacheLevel CacheLevel { get @@ -221,13 +201,7 @@ namespace Umbraco.Core.Models.PublishedContent } } - /// - /// Converts the source value into the intermediate value. - /// - /// The published element owning the property. - /// The source value. - /// A value indicating whether content should be considered draft. - /// The intermediate value. + /// public object ConvertSourceToInter(IPublishedElement owner, object source, bool preview) { if (!_initialized) Initialize(); @@ -238,14 +212,7 @@ namespace Umbraco.Core.Models.PublishedContent : source; } - /// - /// Converts the intermediate value into the object value. - /// - /// The published element owning the property. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether content should be considered draft. - /// The object value. + /// public object ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { if (!_initialized) Initialize(); @@ -256,17 +223,7 @@ namespace Umbraco.Core.Models.PublishedContent : inter; } - /// - /// Converts the intermediate value into the XPath value. - /// - /// The published element owning the property. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether content should be considered draft. - /// The XPath value. - /// - /// The XPath value can be either a string or an XPathNavigator. - /// + /// public object ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { if (!_initialized) Initialize(); @@ -282,13 +239,7 @@ namespace Umbraco.Core.Models.PublishedContent return inter.ToString().Trim(); } - /// - /// Gets the property model CLR type. - /// - /// - /// The model CLR type may be a type, or may contain types. - /// For the actual CLR type, see . - /// + /// public Type ModelClrType { get @@ -298,14 +249,7 @@ namespace Umbraco.Core.Models.PublishedContent } } - /// - /// Gets the property CLR type. - /// - /// - /// Returns the actual CLR type which does not contain types. - /// Mapping from may throw if some instances - /// could not be mapped to actual CLR types. - /// + /// public Type ClrType { get diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index 7469222ab0..10f999532f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -38,7 +38,7 @@ namespace Umbraco.Core.Models.PublishedContent public override object GetXPathValue(string culture = null, string segment = null) => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _xpathValue.Value : null; - public RawValueProperty(PublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) + public RawValueProperty(IPublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { if (propertyType.Variations != ContentVariation.Nothing) diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index f6a7cbf32f..0a9cf632bc 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.PropertyEditors /// /// The property type. /// A value indicating whether the converter supports a property type. - bool IsConverter(PublishedPropertyType propertyType); + bool IsConverter(IPublishedPropertyType propertyType); /// /// Determines whether a value is an actual value, or not a value. @@ -36,14 +36,14 @@ namespace Umbraco.Core.PropertyEditors /// The CLR type of values returned by the converter. /// Some of the CLR types may be generated, therefore this method cannot directly return /// a Type object (which may not exist yet). In which case it needs to return a ModelType instance. - Type GetPropertyValueType(PublishedPropertyType propertyType); + Type GetPropertyValueType(IPublishedPropertyType propertyType); /// /// Gets the property cache level. /// /// The property type. /// The property cache level. - PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType); + PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType); /// /// Converts a property source value to an intermediate value. @@ -64,7 +64,7 @@ namespace Umbraco.Core.PropertyEditors /// strings, and xml-whitespace strings appropriately, ie it should know whether to preserve /// white spaces. /// - object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview); + object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview); /// /// Converts a property intermediate value to an Object value. @@ -83,7 +83,7 @@ namespace Umbraco.Core.PropertyEditors /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage /// the cache levels of property values. It is not meant to be used by the converter. /// - object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); /// /// Converts a property intermediate value to an XPath value. @@ -107,6 +107,6 @@ namespace Umbraco.Core.PropertyEditors /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage /// the cache levels of property values. It is not meant to be used by the converter. /// - object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); + object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview); } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 48bfc49ed9..3b6ebc610c 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.PropertyEditors /// public abstract class PropertyValueConverterBase : IPropertyValueConverter { - public virtual bool IsConverter(PublishedPropertyType propertyType) + public virtual bool IsConverter(IPublishedPropertyType propertyType) => false; public virtual bool? IsValue(object value, PropertyValueLevel level) @@ -30,19 +30,19 @@ namespace Umbraco.Core.PropertyEditors return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); } - public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) + public virtual Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (object); - public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public virtual PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - public virtual object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public virtual object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => source; - public virtual object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public virtual object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => inter; - public virtual object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public virtual object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => inter?.ToString() ?? string.Empty; } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs index 3d69c37b8b..dd2dfb49e7 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs @@ -9,16 +9,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class CheckboxListValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.CheckBoxList); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { var sourceString = source?.ToString() ?? string.Empty; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs index 9f260fc973..46dae3e4f0 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs @@ -8,16 +8,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class ColorPickerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.ColorPicker); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => UseLabel(propertyType) ? typeof(PickedColor) : typeof(string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var useLabel = UseLabel(propertyType); @@ -39,7 +39,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return ssource; } - private bool UseLabel(PublishedPropertyType propertyType) + private bool UseLabel(IPublishedPropertyType propertyType) { return ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration).UseLabel; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index ffe9feb653..0206528bf7 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -8,16 +8,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class DatePickerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.DateTime); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (DateTime); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return DateTime.MinValue; @@ -39,7 +39,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // default ConvertSourceToObject just returns source ie a DateTime value - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a DateTime already return XmlConvert.ToString((DateTime) inter, XmlDateTimeSerializationMode.Unspecified); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs index 6f7888aee3..7d6e7c0ce9 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs @@ -7,16 +7,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class DecimalValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.Decimal.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (decimal); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) { diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs index e4ef3a50a3..88061a559e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class EmailAddressValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.EmailAddress); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { return source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs index 29f6de0271..b3685457ec 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs @@ -28,16 +28,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters _config = config; } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Grid); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (JToken); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs index 79cb748960..6f5bd571b7 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -14,19 +14,19 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public class ImageCropperValueConverter : PropertyValueConverterBase { /// - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.ImageCropper); /// - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (ImageCropperValue); /// - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; /// - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs index e0abf17a7e..ca8f23bca2 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class IntegerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.Integer.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (int); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { return source.TryConvertTo().Result; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs index e04893716a..12e6238705 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/JsonValueConverter.cs @@ -31,19 +31,19 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters /// /// /// - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) { return _propertyEditors.TryGet(propertyType.EditorAlias, out var editor) && editor.GetValueEditor().ValueType.InvariantEquals(ValueTypes.Json); } - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (JToken); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs index 05a5f15aaf..84baf226cf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs @@ -16,10 +16,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class LabelValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.Label.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) { var valueType = ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); switch (valueType.ValueType) @@ -40,10 +40,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters } } - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var valueType = ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); switch (valueType.ValueType) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index aeacf33eef..a062561ab1 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -7,17 +7,17 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MarkdownEditorValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MarkdownEditor.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IHtmlString); // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // in xml a string is: string // in the database a string is: string @@ -25,13 +25,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return source; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return new HtmlString(inter == null ? string.Empty : (string) inter); } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter?.ToString() ?? string.Empty; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs index bdd09ea33b..cd7f48f510 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MemberGroupPickerValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberGroupPicker); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { return source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index 1d5f0b1ca3..15e7ce4caf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -9,18 +9,18 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MultipleTextStringValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MultipleTextstring.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" }; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // data is (both in database and xml): // @@ -58,7 +58,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters : values.ToArray(); } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { var d = new XmlDocument(); var e = d.CreateElement("values"); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs index c9528c3e8b..b9c61bb169 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs @@ -22,16 +22,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters Constants.PropertyEditors.Aliases.MultiNodeTreePicker }; - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Aliases.Contains(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { return source?.ToString(); } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs index b99cc7e0e3..61adc9a93e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class RadioButtonListValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var attempt = source.TryConvertTo(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs index 31ab47223f..11502687b7 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs @@ -18,16 +18,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => IsRangeDataType(propertyType.DataType.Id) ? typeof (Range) : typeof (decimal); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { if (source == null) return null; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs index 9b857c2dff..b54c693c14 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs @@ -19,16 +19,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Tags); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return Array.Empty(); @@ -43,7 +43,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return source.ToString().Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { return (string[]) source; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs index 2368a1d034..7caa9a90cc 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -13,16 +13,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters Constants.PropertyEditors.Aliases.TextArea }; - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => PropertyTypeAliases.Contains(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // in xml a string is: string // in the database a string is: string @@ -30,13 +30,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return source; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter ?? string.Empty; } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs index 46f660d829..9938af671d 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs @@ -10,17 +10,17 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class TinyMceValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IHtmlString); // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // in xml a string is: string // in the database a string is: string @@ -28,13 +28,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return source; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return new HtmlString(inter == null ? string.Empty : (string)inter); } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs index cfa247edaa..407ed13ddf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs @@ -9,16 +9,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class UploadPropertyConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.UploadField); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { return source?.ToString() ?? ""; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index 8ad09733f8..153462ccf5 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -6,16 +6,16 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class YesNoValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.Boolean; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (bool); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { // in xml a boolean is: string // in the database a boolean is: string "1" or "0" or empty @@ -49,7 +49,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // default ConvertSourceToObject just returns source ie a boolean value - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a boolean already return (bool)inter ? "1" : "0"; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d6d0a1d9e5..20cc089d1d 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -222,6 +222,7 @@ + diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs index 0c90c8d1ff..7d2fa74aa6 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedProperty.cs @@ -50,7 +50,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override object GetXPathValue(string culture = null, string segment = null) { throw new NotImplementedException(); } - public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, XmlNode propertyXmlData) + public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, XmlNode propertyXmlData) : this(propertyType, content, isPreviewing) { if (propertyXmlData == null) @@ -58,7 +58,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _sourceValue = XmlHelper.GetNodeValue(propertyXmlData); } - public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, string propertyData) + public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, string propertyData) : this(propertyType, content, isPreviewing) { if (propertyData == null) @@ -66,7 +66,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _sourceValue = propertyData; } - public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing) + public XmlPublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { _sourceValue = string.Empty; diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 0fce8ebfc3..f753acf82d 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -37,10 +37,12 @@ namespace Umbraco.Tests.Published var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - contentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); @@ -70,22 +72,22 @@ namespace Umbraco.Tests.Published } } - public bool IsConverter(PublishedPropertyType propertyType) + public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); - public Type GetPropertyValueType(PublishedPropertyType propertyType) + public Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (int); - public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => int.TryParse(source as string, out int i) ? i : 0; - public object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => (int) inter; - public object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => ((int) inter).ToString(); } @@ -115,10 +117,12 @@ namespace Umbraco.Tests.Published var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - contentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); @@ -143,26 +147,26 @@ namespace Umbraco.Tests.Published public bool? IsValue(object value, PropertyValueLevel level) => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); - public bool IsConverter(PublishedPropertyType propertyType) + public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); - public Type GetPropertyValueType(PublishedPropertyType propertyType) + public Type GetPropertyValueType(IPublishedPropertyType propertyType) // the first version would be the "generic" version, but say we want to be more precise // and return: whatever Clr type is generated for content type with alias "cnt1" -- which // we cannot really typeof() at the moment because it has not been generated, hence ModelType. // => typeof (IPublishedContent); => ModelType.For("cnt1"); - public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => _cacheLevel; - public object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => int.TryParse(source as string, out int i) ? i : -1; - public object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById((int) inter); - public object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => ((int) inter).ToString(); } @@ -208,25 +212,15 @@ namespace Umbraco.Tests.Published var contentTypeFactory = new PublishedContentTypeFactory(factory, converters, dataTypeService); - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType, int i) { - contentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return contentTypeFactory.CreatePropertyType(contentType, "prop" + i, i); + } - var elementType2 = contentTypeFactory.CreateContentType(1001, "element2", new[] - { - contentTypeFactory.CreatePropertyType("prop2", 2), - }); - - var contentType1 = contentTypeFactory.CreateContentType(1002, "content1", new[] - { - contentTypeFactory.CreatePropertyType("prop1", 1), - }); - - var contentType2 = contentTypeFactory.CreateContentType(1003, "content2", new[] - { - contentTypeFactory.CreatePropertyType("prop2", 2), - }); + var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", t => CreatePropertyTypes(t, 1)); + var elementType2 = contentTypeFactory.CreateContentType(1001, "element2", t => CreatePropertyTypes(t, 2)); + var contentType1 = contentTypeFactory.CreateContentType(1002, "content1", t => CreatePropertyTypes(t, 1)); + var contentType2 = contentTypeFactory.CreateContentType(1003, "content2", t => CreatePropertyTypes(t, 2)); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); var element2 = new PublishedElement(elementType2, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); @@ -267,13 +261,13 @@ namespace Umbraco.Tests.Published public class SimpleConverter3A : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == "Umbraco.Void"; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; } @@ -286,22 +280,22 @@ namespace Umbraco.Tests.Published _publishedSnapshotAccessor = publishedSnapshotAccessor; } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == "Umbraco.Void.2"; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable<>).MakeGenericType(ModelType.For("content1")); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var s = source as string; return s?.Split(',').Select(int.Parse).ToArray() ?? Array.Empty(); } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { return ((int[]) inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1) _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(x)).ToArray(); } diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 35be4b0bf2..09c63dcf06 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -125,13 +125,24 @@ namespace Umbraco.Tests.Published var factory = new PublishedContentTypeFactory(publishedModelFactory.Object, converters, dataTypeService); - var propertyType1 = factory.CreatePropertyType("property1", 1); - var propertyType2 = factory.CreatePropertyType("property2", 2); - var propertyTypeN1 = factory.CreatePropertyType("propertyN1", 3); + IEnumerable CreatePropertyTypes1(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "property1", 1); + } - var contentType1 = factory.CreateContentType(1, "content1", new[] { propertyType1 }); - var contentType2 = factory.CreateContentType(2, "content2", new[] { propertyType2 }); - var contentTypeN1 = factory.CreateContentType(2, "contentN1", new[] { propertyTypeN1 }, isElement: true); + IEnumerable CreatePropertyTypes2(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "property2", 2); + } + + IEnumerable CreatePropertyTypesN1(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "propertyN1", 3); + } + + var contentType1 = factory.CreateContentType(1, "content1", CreatePropertyTypes1); + var contentType2 = factory.CreateContentType(2, "content2", CreatePropertyTypes2); + var contentTypeN1 = factory.CreateContentType(2, "contentN1", CreatePropertyTypesN1, isElement: true); // mocked content cache returns content types contentCache @@ -219,14 +230,14 @@ namespace Umbraco.Tests.Published private readonly bool _hasValue; private IPublishedElement _owner; - public TestPublishedProperty(PublishedPropertyType propertyType, object source) + public TestPublishedProperty(IPublishedPropertyType propertyType, object source) : base(propertyType, PropertyCacheLevel.Element) // initial reference cache level always is .Content { _sourceValue = source; _hasValue = source != null && (!(source is string ssource) || !string.IsNullOrWhiteSpace(ssource)); } - public TestPublishedProperty(PublishedPropertyType propertyType, IPublishedElement element, bool preview, PropertyCacheLevel referenceCacheLevel, object source) + public TestPublishedProperty(IPublishedPropertyType propertyType, IPublishedElement element, bool preview, PropertyCacheLevel referenceCacheLevel, object source) : base(propertyType, referenceCacheLevel) { _sourceValue = source; diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 76fdd81ec2..9db539d142 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -35,10 +35,13 @@ namespace Umbraco.Tests.Published new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", new[] + + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - publishedContentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); // PublishedElementPropertyBase.GetCacheLevels: // @@ -113,10 +116,13 @@ namespace Umbraco.Tests.Published new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", new[] + + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - publishedContentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); var elementsCache = new FastDictionaryAppCache(); var snapshotCache = new FastDictionaryAppCache(); @@ -187,10 +193,13 @@ namespace Umbraco.Tests.Published new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", new[] + + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - publishedContentTypeFactory.CreatePropertyType("prop1", 1), - }); + yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); + } + + var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); Assert.Throws(() => { @@ -213,28 +222,28 @@ namespace Umbraco.Tests.Published public bool? IsValue(object value, PropertyValueLevel level) => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); - public bool IsConverter(PublishedPropertyType propertyType) + public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); - public Type GetPropertyValueType(PublishedPropertyType propertyType) + public Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(int); - public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => _cacheLevel; - public object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { SourceConverts++; return int.TryParse(source as string, out int i) ? i : 0; } - public object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { InterConverts++; return (int) inter; } - public object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => ((int) inter).ToString(); } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 108bfb9f18..5c28b98de7 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -65,17 +65,22 @@ namespace Umbraco.Tests.PublishedContent var welcome2Type = factory.CreatePropertyType("welcomeText2", 1, variations: ContentVariation.Culture); var nopropType = factory.CreatePropertyType("noprop", 1, variations: ContentVariation.Culture); - var props = new[] - { - prop1Type, - welcomeType, - welcome2Type, - nopropType - }; - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), props); + IEnumerable CreatePropertyTypes1(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "prop1", 1, variations: ContentVariation.Culture); + yield return factory.CreatePropertyType(contentType, "welcomeText", 1, variations: ContentVariation.Culture); + yield return factory.CreatePropertyType(contentType, "welcomeText2", 1, variations: ContentVariation.Culture); + yield return factory.CreatePropertyType(contentType, "noprop", 1, variations: ContentVariation.Culture); + } - var prop3Type = factory.CreatePropertyType("prop3", 1, variations: ContentVariation.Culture); - var contentType2 = factory.CreateContentType(2, "contentType2", Enumerable.Empty(), new[] { prop3Type }); + var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes1); + + IEnumerable CreatePropertyTypes2(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "prop3", 1, variations: ContentVariation.Culture); + } + + var contentType2 = factory.CreateContentType(2, "contentType2", Enumerable.Empty(), CreatePropertyTypes2); var prop1 = new SolidPublishedPropertyWithLanguageVariants { @@ -150,7 +155,7 @@ namespace Umbraco.Tests.PublishedContent var prop4 = new SolidPublishedPropertyWithLanguageVariants { Alias = "prop3", - PropertyType = prop3Type + PropertyType = contentType2.GetPropertyType("prop3") }; prop4.SetSourceValue("en-US", "Oxxo", true); prop4.SetValue("en-US", "Oxxo", true); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index b2f1f311c3..48ad4aa464 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -1,4 +1,5 @@ -using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.PublishedContent; @@ -15,13 +16,14 @@ namespace Umbraco.Tests.PublishedContent { internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache) { - var props = new[] - { - factory.CreatePropertyType("prop1", 1), - }; - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), props); - var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), props); - var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), props); + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "prop1", 1); + } + + var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes); + var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), CreatePropertyTypes); + var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), CreatePropertyTypes); cache.Add(new SolidPublishedContent(contentType1) { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index c5bcd29589..6907f6936c 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -1,4 +1,5 @@ -using Umbraco.Core; +using System.Collections.Generic; +using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; @@ -41,13 +42,12 @@ namespace Umbraco.Tests.PublishedContent var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeService); - // need to specify a custom callback for unit tests - var propertyTypes = new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { - // AutoPublishedContentType will auto-generate other properties - publishedContentTypeFactory.CreatePropertyType("content", 1), - }; - var type = new AutoPublishedContentType(0, "anything", propertyTypes); + yield return publishedContentTypeFactory.CreatePropertyType(contentType, "content", 1); + } + + var type = new AutoPublishedContentType(0, "anything", CreatePropertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; var umbracoContext = GetUmbracoContext("/test"); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 605fae7a1b..8be13345fb 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Web; @@ -62,18 +63,19 @@ namespace Umbraco.Tests.PublishedContent // when they are requested, but we must declare those that we // explicitely want to be here... - var propertyTypes = new[] + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) { // AutoPublishedContentType will auto-generate other properties - factory.CreatePropertyType("umbracoNaviHide", 1001), - factory.CreatePropertyType("selectedNodes", 1), - factory.CreatePropertyType("umbracoUrlAlias", 1), - factory.CreatePropertyType("content", 1002), - factory.CreatePropertyType("testRecursive", 1), - }; + yield return factory.CreatePropertyType(contentType, "umbracoNaviHide", 1001); + yield return factory.CreatePropertyType(contentType, "selectedNodes", 1); + yield return factory.CreatePropertyType(contentType, "umbracoUrlAlias", 1); + yield return factory.CreatePropertyType(contentType, "content", 1002); + yield return factory.CreatePropertyType(contentType, "testRecursive", 1); + } + var compositionAliases = new[] { "MyCompositionAlias" }; - var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, propertyTypes); - var homeType = new AutoPublishedContentType(0, "home", compositionAliases, propertyTypes); + var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, CreatePropertyTypes); + var homeType = new AutoPublishedContentType(0, "home", compositionAliases, CreatePropertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType; } @@ -887,8 +889,13 @@ namespace Umbraco.Tests.PublishedContent { var factory = Factory.GetInstance() as PublishedContentTypeFactory; - var pt = factory.CreatePropertyType("detached", 1003); - var ct = factory.CreateContentType(0, "alias", new[] { pt }); + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "detached", 1003); + } + + var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes); + var pt = ct.GetPropertyType("detached"); var prop = new PublishedElementPropertyBase(pt, null, false, PropertyCacheLevel.None, 5548); Assert.IsInstanceOf(prop.GetValue()); Assert.AreEqual(5548, prop.GetValue()); @@ -906,16 +913,20 @@ namespace Umbraco.Tests.PublishedContent { var factory = Factory.GetInstance() as PublishedContentTypeFactory; - var pt1 = factory.CreatePropertyType("legend", 1004); - var pt2 = factory.CreatePropertyType("image", 1005); - var pt3 = factory.CreatePropertyType("size", 1003); + IEnumerable CreatePropertyTypes(IPublishedContentType contentType) + { + yield return factory.CreatePropertyType(contentType, "legend", 1004); + yield return factory.CreatePropertyType(contentType, "image", 1005); + yield return factory.CreatePropertyType(contentType, "size", 1003); + } + const string val1 = "boom bam"; const int val2 = 0; const int val3 = 666; var guid = Guid.NewGuid(); - var ct = factory.CreateContentType(0, "alias", new[] { pt1, pt2, pt3 }); + var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes); var c = new ImageWithLegendModel(ct, guid, new Dictionary { diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index c14a8c1740..f7d016f725 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -253,7 +253,7 @@ namespace Umbraco.Tests.PublishedContent internal class SolidPublishedProperty : IPublishedProperty { - public PublishedPropertyType PropertyType { get; set; } + public IPublishedPropertyType PropertyType { get; set; } public string Alias { get; set; } public object SolidSourceValue { get; set; } public object SolidValue { get; set; } @@ -397,7 +397,7 @@ namespace Umbraco.Tests.PublishedContent class AutoPublishedContentType : PublishedContentType { - private static readonly PublishedPropertyType Default; + private static readonly IPublishedPropertyType Default; static AutoPublishedContentType() { @@ -412,11 +412,19 @@ namespace Umbraco.Tests.PublishedContent : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) { } + public AutoPublishedContentType(int id, string alias, Func> propertyTypes) + : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) + { } + public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) { } - public override PublishedPropertyType GetPropertyType(string alias) + public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes) + : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) + { } + + public override IPublishedPropertyType GetPropertyType(string alias) { var propertyType = base.GetPropertyType(alias); return propertyType ?? Default; diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 0ff2a41867..978bbbcecb 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -140,14 +140,14 @@ namespace Umbraco.Web.Macros private readonly object _sourceValue; private readonly IPublishedContent _content; - public PagePublishedProperty(PublishedPropertyType propertyType, IPublishedContent content) + public PagePublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { _sourceValue = null; _content = content; } - public PagePublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, Umbraco.Core.Models.Property property) + public PagePublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, Umbraco.Core.Models.Property property) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { _sourceValue = property.GetValue(); diff --git a/src/Umbraco.Web/Models/PublishedProperty.cs b/src/Umbraco.Web/Models/PublishedProperty.cs index d32612c54c..f715df7450 100644 --- a/src/Umbraco.Web/Models/PublishedProperty.cs +++ b/src/Umbraco.Web/Models/PublishedProperty.cs @@ -19,8 +19,8 @@ namespace Umbraco.Web.Models /// and taking values from the collection of Property. /// Ensures that all conversions took place correctly. internal static IEnumerable MapProperties( - IEnumerable propertyTypes, IEnumerable properties, - Func map) + IEnumerable propertyTypes, IEnumerable properties, + Func map) { var propertyEditors = Current.PropertyEditors; var dataTypeService = Current.Services.DataTypeService; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs index d30762f13f..056334cfa5 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -23,16 +23,16 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters _publishedSnapshotAccessor = publishedSnapshotAccessor; } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.ContentPicker); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IPublishedContent); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; @@ -45,7 +45,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return null; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { if (inter == null) return null; @@ -73,7 +73,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return inter; } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { if (inter == null) return null; return inter.ToString(); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs index 43add34327..0d6089f0f4 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs @@ -11,12 +11,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class FlexibleDropdownPropertyValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) { return propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.DropDownListFlexible); } - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if(source == null) return Array.Empty(); @@ -24,7 +24,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return JsonConvert.DeserializeObject(source.ToString()) ?? Array.Empty(); } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { if (inter == null) return null; @@ -43,7 +43,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters : string.Empty; } - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) { return propertyType.DataType.ConfigurationAs().Multiple ? typeof(IEnumerable) diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs index e6e66b79ff..ef356e3604 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MacroContainerValueConverter.cs @@ -28,13 +28,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters _macroRenderer = macroRenderer ?? throw new ArgumentNullException(nameof(macroRenderer)); } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.MacroContainer; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IHtmlString); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; // NOT thread-safe over a request because it modifies the @@ -63,7 +63,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } } - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index cb9709157f..e11f3e0d3a 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -12,16 +12,16 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class MarkdownEditorValueConverter : PropertyValueConverterBase { - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MarkdownEditor == propertyType.EditorAlias; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IHtmlString); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); @@ -33,7 +33,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return sourceString; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // convert markup to HTML for frontend rendering. // source should come from ConvertSource and be a string (or null) already diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs index 0218867bb4..d2272a25b5 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs @@ -29,12 +29,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters _publishedModelFactory = publishedModelFactory; } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) { return propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MediaPicker); } - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) { var isMultiple = IsMultipleDataType(propertyType.DataType); var isOnlyImages = IsOnlyImagesDataType(propertyType.DataType); @@ -48,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters : typeof(IPublishedContent); } - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; private bool IsMultipleDataType(PublishedDataType dataType) @@ -63,7 +63,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return config.OnlyImages; } - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; @@ -75,7 +75,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return nodeIds; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { var isMultiple = IsMultipleDataType(propertyType.DataType); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs index b4c7f99a75..cd69fd9de6 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs @@ -18,18 +18,18 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) { return propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberPicker); } - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IPublishedContent); - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { var attemptConvertInt = source.TryConvertTo(); if (attemptConvertInt.Success) @@ -40,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return null; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { if (source == null) return null; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index a210aa62c6..f80f8f510f 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -34,18 +34,18 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); } - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) { return propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker); } - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (IEnumerable); - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; @@ -60,7 +60,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return null; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview) { if (source == null) { diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs index 48cec2a3d2..87ab5b8ff9 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs @@ -23,20 +23,20 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters _proflog = proflog ?? throw new ArgumentNullException(nameof(proflog)); } - public override bool IsConverter(PublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MultiUrlPicker.Equals(propertyType.EditorAlias); + public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MultiUrlPicker.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) => + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => propertyType.DataType.ConfigurationAs().MaxNumber == 1 ? typeof(Link) : typeof(IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; public override bool? IsValue(object value, PropertyValueLevel level) => value?.ToString() != "[]"; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) => source?.ToString(); + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => source?.ToString(); - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { using (_proflog.DebugDuration($"ConvertPropertyToLinks ({propertyType.DataType.Id})")) { diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs index 93e4afc7e5..74b2af6498 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs @@ -28,11 +28,11 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } /// - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => IsNestedMany(propertyType); /// - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) { var contentTypes = propertyType.DataType.ConfigurationAs().ContentTypes; return contentTypes.Length > 1 @@ -41,19 +41,19 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } /// - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; /// - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { return source?.ToString(); } /// - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { - using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})")) + using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})")) { var configuration = propertyType.DataType.ConfigurationAs(); var contentTypes = configuration.ContentTypes; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs index e084b3a343..06aa0b42fb 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs @@ -27,11 +27,11 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } /// - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => IsNestedSingle(propertyType); /// - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) { var contentTypes = propertyType.DataType.ConfigurationAs().ContentTypes; return contentTypes.Length > 1 @@ -40,19 +40,19 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } /// - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; /// - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { return source?.ToString(); } /// - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { - using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})")) + using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})")) { var value = (string) inter; if (string.IsNullOrWhiteSpace(value)) return null; diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs index e3723e2221..7c18d8ebca 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentValueConverterBase.cs @@ -20,12 +20,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters protected IPublishedModelFactory PublishedModelFactory { get; } - public static bool IsNested(PublishedPropertyType publishedProperty) + public static bool IsNested(IPublishedPropertyType publishedProperty) { return publishedProperty.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.NestedContent); } - public static bool IsNestedSingle(PublishedPropertyType publishedProperty) + public static bool IsNestedSingle(IPublishedPropertyType publishedProperty) { if (!IsNested(publishedProperty)) return false; @@ -34,7 +34,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return config.MinItems == 1 && config.MaxItems == 1; } - public static bool IsNestedMany(PublishedPropertyType publishedProperty) + public static bool IsNestedMany(IPublishedPropertyType publishedProperty) { return IsNested(publishedProperty) && !IsNestedSingle(publishedProperty); } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index fbbf058c49..990ef8daf1 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) { // because that version of RTE converter parses {locallink} and executes macros, its value has // to be cached at the published snapshot level, because we have no idea what the macros may depend on actually. @@ -63,7 +63,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } } - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) { diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs index 2d5c322f58..b8ad1477b4 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/TextStringValueConverter.cs @@ -17,16 +17,16 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters Constants.PropertyEditors.Aliases.TextArea }; - public override bool IsConverter(PublishedPropertyType propertyType) + public override bool IsConverter(IPublishedPropertyType propertyType) => PropertyTypeAliases.Contains(propertyType.EditorAlias); - public override Type GetPropertyValueType(PublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof (string); - public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { if (source == null) return null; var sourceString = source.ToString(); @@ -38,13 +38,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return sourceString; } - public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter ?? string.Empty; } - public override object ConvertIntermediateToXPath(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { // source should come from ConvertSource and be a string (or null) already return inter; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index 2c8bc94d90..0254b815c1 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -38,12 +38,12 @@ namespace Umbraco.Web.PublishedCache.NuCache private string _valuesCacheKey; // initializes a published content property with no value - public Property(PublishedPropertyType propertyType, PublishedContent content, IPublishedSnapshotAccessor publishedSnapshotAccessor, PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element) + public Property(IPublishedPropertyType propertyType, PublishedContent content, IPublishedSnapshotAccessor publishedSnapshotAccessor, PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element) : this(propertyType, content, null, publishedSnapshotAccessor, referenceCacheLevel) { } // initializes a published content property with a value - public Property(PublishedPropertyType propertyType, PublishedContent content, PropertyData[] sourceValues, IPublishedSnapshotAccessor publishedSnapshotAccessor, PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element) + public Property(IPublishedPropertyType propertyType, PublishedContent content, PropertyData[] sourceValues, IPublishedSnapshotAccessor publishedSnapshotAccessor, PropertyCacheLevel referenceCacheLevel = PropertyCacheLevel.Element) : base(propertyType, referenceCacheLevel) { if (sourceValues != null) diff --git a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs index 62f72a27aa..ef62a3254d 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.PublishedCache // so making it configurable. private const bool FullCacheWhenPreviewing = true; - public PublishedElementPropertyBase(PublishedPropertyType propertyType, IPublishedElement element, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null, IPublishedSnapshotAccessor publishedSnapshotAccessor = null) + public PublishedElementPropertyBase(IPublishedPropertyType propertyType, IPublishedElement element, bool previewing, PropertyCacheLevel referenceCacheLevel, object sourceValue = null, IPublishedSnapshotAccessor publishedSnapshotAccessor = null) : base(propertyType, referenceCacheLevel) { _sourceValue = sourceValue; From 890d7d8ce32e1e527294b215d66a195c1d1fc8bf Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Apr 2019 16:25:10 +0200 Subject: [PATCH 005/218] Refactor IPublishedContent.Name() --- .../PublishedContent/IPublishedContent.cs | 8 +-- .../PublishedContentWrapped.cs | 2 +- .../PublishedContent/PublishedCultureInfos.cs | 2 +- .../PublishedMediaCacheTests.cs | 2 +- .../DictionaryPublishedContent.cs | 2 +- .../XmlPublishedContent.cs | 9 +-- .../Published/NestedContentTests.cs | 2 +- .../PublishedContent/NuCacheTests.cs | 20 +++---- .../PublishedContentDataTableTests.cs | 7 ++- .../PublishedContentLanguageVariantTests.cs | 6 +- .../PublishedContentMoreTests.cs | 59 ++++++++++--------- .../PublishedContent/PublishedMediaTests.cs | 2 +- .../PublishedContent/PublishedRouterTests.cs | 2 +- .../SolidPublishedSnapshot.cs | 7 ++- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 6 +- .../Scoping/ScopedNuCacheTests.cs | 6 +- .../TestHelpers/Stubs/TestPublishedContent.cs | 5 +- .../Editors/TemplateQueryController.cs | 10 ++-- .../PublishedContentHashtableConverter.cs | 6 +- .../Models/PublishedContentBase.cs | 2 +- .../NuCache/Navigable/NavigableContent.cs | 2 +- .../NuCache/PublishedContent.cs | 19 +++--- .../PublishedCache/PublishedMember.cs | 6 +- src/Umbraco.Web/PublishedContentExtensions.cs | 2 +- .../Routing/UrlProviderExtensions.cs | 2 +- 25 files changed, 102 insertions(+), 94 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index d38c8eb721..95b943d751 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -29,11 +29,9 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the name of the content item. /// - /// - /// The value of this property is contextual. When the content type is multi-lingual, - /// this is the name for the 'current' culture. Otherwise, it is the invariant name. - /// - string Name { get; } + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The name of the content. + string Name(string culture = null); /// /// Gets the url segment of the content item. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 7f3f38f629..753f75c3cb 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -58,7 +58,7 @@ namespace Umbraco.Core.Models.PublishedContent public virtual int Id => _content.Id; /// - public virtual string Name => _content.Name; + public virtual string Name(string culture = null) => _content.Name(culture); /// public virtual string UrlSegment => _content.UrlSegment; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index 749b37a41a..9c5977e8a6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the name of the item. /// - public string Name { get; } + internal string Name { get; } /// /// Gets the url segment of the item. diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index 08eeb8ef4d..8dd5cf3890 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -108,7 +108,7 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(mRoot.ContentType.Alias, publishedMedia.ContentType.Alias); Assert.AreEqual(mRoot.ContentType.Id, publishedMedia.ContentType.Id); Assert.AreEqual(mRoot.Level, publishedMedia.Level); - Assert.AreEqual(mRoot.Name, publishedMedia.Name); + Assert.AreEqual(mRoot.Name, publishedMedia.Name()); Assert.AreEqual(mRoot.Path, publishedMedia.Path); Assert.AreEqual(mRoot.SortOrder, publishedMedia.SortOrder); Assert.IsNull(publishedMedia.Parent); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index e472de85dd..c6c16a2466 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -156,7 +156,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override int SortOrder => _sortOrder; - public override string Name => _name; + public override string Name(string culture = null) => _name; public override PublishedCultureInfo GetCulture(string culture = null) => null; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index 43c47ec569..c547512ba8 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -138,13 +138,10 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } - public override string Name + public override string Name(string culture = null) { - get - { - EnsureNodeInitialized(); - return _name; - } + EnsureNodeInitialized(); + return _name; } public override PublishedCultureInfo GetCulture(string culture = null) => null; diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 09c63dcf06..35288c1f75 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -284,7 +284,7 @@ namespace Umbraco.Tests.Published public override int Id { get; } public override int? TemplateId { get; } public override int SortOrder { get; } - public override string Name { get; } + public override string Name(string culture = null) => default; public override PublishedCultureInfo GetCulture(string culture = ".") => throw new NotSupportedException(); public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); public override string UrlSegment { get; } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index ad2b0220bb..89c2458f18 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -202,35 +202,33 @@ namespace Umbraco.Tests.PublishedContent var publishedContent = snapshot.Content.GetById(1); Assert.IsNotNull(publishedContent); - Assert.AreEqual("It Works1!", publishedContent.Name); Assert.AreEqual("val1", publishedContent.Value("prop")); Assert.AreEqual("val-fr1", publishedContent.Value("prop", "fr-FR")); Assert.AreEqual("val-uk1", publishedContent.Value("prop", "en-UK")); - Assert.AreEqual("name-fr1", publishedContent.GetCulture("fr-FR").Name); - Assert.AreEqual("name-uk1", publishedContent.GetCulture("en-UK").Name); + Assert.IsNull(publishedContent.Name()); // no invariant name for varying content + Assert.AreEqual("name-fr1", publishedContent.Name("fr-FR")); + Assert.AreEqual("name-uk1", publishedContent.Name("en-UK")); var draftContent = snapshot.Content.GetById(true, 1); - Assert.AreEqual("It Works2!", draftContent.Name); Assert.AreEqual("val2", draftContent.Value("prop")); Assert.AreEqual("val-fr2", draftContent.Value("prop", "fr-FR")); Assert.AreEqual("val-uk2", draftContent.Value("prop", "en-UK")); - Assert.AreEqual("name-fr2", draftContent.GetCulture("fr-FR").Name); - Assert.AreEqual("name-uk2", draftContent.GetCulture("en-UK").Name); + Assert.IsNull(draftContent.Name()); // no invariant name for varying content + Assert.AreEqual("name-fr2", draftContent.Name("fr-FR")); + Assert.AreEqual("name-uk2", draftContent.Name("en-UK")); // now french is default _variationAccesor.VariationContext = new VariationContext("fr-FR"); Assert.AreEqual("val-fr1", publishedContent.Value("prop")); - Assert.AreEqual("name-fr1", publishedContent.GetCulture().Name); - Assert.AreEqual("name-fr1", publishedContent.Name); + Assert.AreEqual("name-fr1", publishedContent.Name()); Assert.AreEqual(new DateTime(2018, 01, 01, 01, 00, 00), publishedContent.GetCulture().Date); // now uk is default _variationAccesor.VariationContext = new VariationContext("en-UK"); Assert.AreEqual("val-uk1", publishedContent.Value("prop")); - Assert.AreEqual("name-uk1", publishedContent.GetCulture().Name); - Assert.AreEqual("name-uk1", publishedContent.Name); + Assert.AreEqual("name-uk1", publishedContent.Name()); Assert.AreEqual(new DateTime(2018, 01, 02, 01, 00, 00), publishedContent.GetCulture().Date); // invariant needs to be retrieved explicitly, when it's not default @@ -251,7 +249,7 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.GetPropertyType("prop").Variations); // now, "no culture" means "invariant" - Assert.AreEqual("It Works1!", againContent.Name); + Assert.IsNull(againContent.Name()); // no invariant name for varying content Assert.AreEqual("val1", againContent.Value("prop")); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 74b9619845..220909e237 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -140,7 +140,6 @@ namespace Umbraco.Tests.PublishedContent UpdateDate = DateTime.Now, Path = "-1,3", UrlSegment = "home-page", - Name = "Page" + Guid.NewGuid().ToString(), Version = Guid.NewGuid(), WriterId = 1, WriterName = "Shannon", @@ -148,6 +147,7 @@ namespace Umbraco.Tests.PublishedContent Level = 1, Children = new List() }; + d.SetName("Page" + Guid.NewGuid()); d.Properties = new Collection(new List { new RawValueProperty(factory.CreatePropertyType("property1", 1), d, "value" + indexVals), @@ -183,6 +183,8 @@ namespace Umbraco.Tests.PublishedContent // l8tr... private class TestPublishedContent : IPublishedContent { + private readonly Dictionary _names = new Dictionary(); + public string Url { get; set; } public string GetUrl(string culture = null) => throw new NotSupportedException(); @@ -203,7 +205,8 @@ namespace Umbraco.Tests.PublishedContent public Guid Key { get; set; } public int? TemplateId { get; set; } public int SortOrder { get; set; } - public string Name { get; set; } + public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; + public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); public IReadOnlyDictionary Cultures => throw new NotSupportedException(); public string UrlSegment { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 5c28b98de7..9cec962a38 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -122,7 +122,6 @@ namespace Umbraco.Tests.PublishedContent { Id = 1, SortOrder = 0, - Name = "Content 1", UrlSegment = "content-1", Path = "/1", Level = 1, @@ -134,12 +133,12 @@ namespace Umbraco.Tests.PublishedContent prop1, prop2, noprop } }; + item1.SetName("Content 1"); var item2 = new SolidPublishedContent(contentType1) { Id = 2, SortOrder = 0, - Name = "Content 2", UrlSegment = "content-2", Path = "/1/2", Level = 2, @@ -151,6 +150,7 @@ namespace Umbraco.Tests.PublishedContent prop3 } }; + item2.SetName("Content 2"); var prop4 = new SolidPublishedPropertyWithLanguageVariants { @@ -164,7 +164,6 @@ namespace Umbraco.Tests.PublishedContent { Id = 3, SortOrder = 0, - Name = "Content 3", UrlSegment = "content-3", Path = "/1/2/3", Level = 3, @@ -176,6 +175,7 @@ namespace Umbraco.Tests.PublishedContent prop4 } }; + item3.SetName("Content 3"); item1.Children = new List { item2 }; item2.Parent = item1; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 48ad4aa464..ffe88f5d13 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -25,11 +25,10 @@ namespace Umbraco.Tests.PublishedContent var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), CreatePropertyTypes); var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), CreatePropertyTypes); - cache.Add(new SolidPublishedContent(contentType1) + var content = new SolidPublishedContent(contentType1) { Id = 1, SortOrder = 0, - Name = "Content 1", UrlSegment = "content-1", Path = "/1", Level = 1, @@ -37,22 +36,23 @@ namespace Umbraco.Tests.PublishedContent ParentId = -1, ChildIds = new int[] { }, Properties = new Collection + { + new SolidPublishedProperty { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } + Alias = "prop1", + SolidHasValue = true, + SolidValue = 1234, + SolidSourceValue = "1234" } - }); + } + }; + content.SetName("Content 1"); + cache.Add(content); - cache.Add(new SolidPublishedContent(contentType2) + content = new SolidPublishedContent(contentType2) { Id = 2, SortOrder = 1, - Name = "Content 2", UrlSegment = "content-2", Path = "/2", Level = 1, @@ -60,22 +60,23 @@ namespace Umbraco.Tests.PublishedContent ParentId = -1, ChildIds = new int[] { }, Properties = new Collection + { + new SolidPublishedProperty { - new SolidPublishedProperty - { - Alias = "prop1", - SolidHasValue = true, - SolidValue = 1234, - SolidSourceValue = "1234" - } + Alias = "prop1", + SolidHasValue = true, + SolidValue = 1234, + SolidSourceValue = "1234" } - }); + } + }; + content.SetName("Content 2"); + cache.Add(content); - cache.Add(new SolidPublishedContent(contentType2Sub) + content = new SolidPublishedContent(contentType2Sub) { Id = 3, SortOrder = 2, - Name = "Content 2Sub", UrlSegment = "content-2sub", Path = "/3", Level = 1, @@ -92,14 +93,16 @@ namespace Umbraco.Tests.PublishedContent SolidSourceValue = "1234" } } - }); + }; + content.SetName("Content 2Sub"); + cache.Add(content); } [Test] public void First() { var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); - Assert.AreEqual("Content 1", content.Name); + Assert.AreEqual("Content 1", content.Name()); } [Test] @@ -111,17 +114,17 @@ namespace Umbraco.Tests.PublishedContent .ToIndexedArray(); var item = items[0]; - Assert.AreEqual("Content 1", item.Content.Name); + Assert.AreEqual("Content 1", item.Content.Name()); Assert.IsTrue(item.IsFirst()); Assert.IsFalse(item.IsLast()); item = items[1]; - Assert.AreEqual("Content 2", item.Content.Name); + Assert.AreEqual("Content 2", item.Content.Name()); Assert.IsFalse(item.IsFirst()); Assert.IsFalse(item.IsLast()); item = items[2]; - Assert.AreEqual("Content 2Sub", item.Content.Name); + Assert.AreEqual("Content 2Sub", item.Content.Name()); Assert.IsFalse(item.IsFirst()); Assert.IsTrue(item.IsLast()); } @@ -154,7 +157,7 @@ namespace Umbraco.Tests.PublishedContent var content = Current.UmbracoContext.ContentCache.GetAtRoot() .OfType() .First(x => x.Prop1 == 1234); - Assert.AreEqual("Content 2", content.Name); + Assert.AreEqual("Content 2", content.Name()); Assert.AreEqual(1234, content.Prop1); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index f801d02c5b..b789eb0ef8 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -495,7 +495,7 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(nodeId, converted.Id); Assert.AreEqual(3, converted.Level); Assert.AreEqual(1, converted.SortOrder); - Assert.AreEqual("Sam's Umbraco Image", converted.Name); + Assert.AreEqual("Sam's Umbraco Image", converted.Name()); Assert.AreEqual("-1,1111,2222,2112", converted.Path); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index d8dbabb569..19944a2cd4 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -58,7 +58,7 @@ namespace Umbraco.Tests.PublishedContent { var pc = new Mock(); pc.Setup(content => content.Id).Returns(1); - pc.Setup(content => content.Name).Returns("test"); + pc.Setup(content => content.Name(It.IsAny())).Returns("test"); pc.Setup(content => content.WriterName).Returns("admin"); pc.Setup(content => content.CreatorName).Returns("admin"); pc.Setup(content => content.CreateDate).Returns(DateTime.Now); diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index f7d016f725..a25faea07a 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -153,8 +153,10 @@ namespace Umbraco.Tests.PublishedContent } } - class SolidPublishedContent : IPublishedContent + internal class SolidPublishedContent : IPublishedContent { + private readonly Dictionary _names = new Dictionary(); + #region Constructor public SolidPublishedContent(IPublishedContentType contentType) @@ -177,7 +179,8 @@ namespace Umbraco.Tests.PublishedContent public Guid Key { get; set; } public int? TemplateId { get; set; } public int SortOrder { get; set; } - public string Name { get; set; } + public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; + public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); public IReadOnlyDictionary Cultures => throw new NotSupportedException(); public string UrlSegment { get; set; } diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 0f99b6b884..e07ae576e6 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -190,7 +190,7 @@ namespace Umbraco.Tests.Runtimes // but a draft document pcontent = umbracoContext.ContentCache.GetById(true, content.Id); Assert.IsNotNull(pcontent); - Assert.AreEqual("test", pcontent.Name); + Assert.AreEqual("test", pcontent.Name()); Assert.IsTrue(pcontent.IsDraft()); // no published url @@ -204,7 +204,7 @@ namespace Umbraco.Tests.Runtimes // assert that snapshot has been updated and there is now a published document pcontent = umbracoContext.ContentCache.GetById(content.Id); Assert.IsNotNull(pcontent); - Assert.AreEqual("test", pcontent.Name); + Assert.AreEqual("test", pcontent.Name()); Assert.IsFalse(pcontent.IsDraft()); // but the url is the published one - no draft url @@ -213,7 +213,7 @@ namespace Umbraco.Tests.Runtimes // and also an updated draft document pcontent = umbracoContext.ContentCache.GetById(true, content.Id); Assert.IsNotNull(pcontent); - Assert.AreEqual("testx", pcontent.Name); + Assert.AreEqual("testx", pcontent.Name()); Assert.IsTrue(pcontent.IsDraft()); // and the published document has a url diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 1dcc928141..f0efffc2cc 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -152,7 +152,7 @@ namespace Umbraco.Tests.Scoping // during events, due to LiveSnapshot, we see the changes Assert.IsNotNull(e); - Assert.AreEqual("changed", e.Name); + Assert.AreEqual("changed", e.Name()); }; using (var scope = ScopeProvider.CreateScope()) @@ -164,7 +164,7 @@ namespace Umbraco.Tests.Scoping // been created var x = umbracoContext.ContentCache.GetById(item.Id); Assert.IsNotNull(x); - Assert.AreEqual("name", x.Name); + Assert.AreEqual("name", x.Name()); ContentService.Published += OnPublishedAssert; @@ -186,7 +186,7 @@ namespace Umbraco.Tests.Scoping // else changes have been rolled back x = umbracoContext.ContentCache.GetById(item.Id); Assert.IsNotNull(x); - Assert.AreEqual(complete ? "changed" : "name", x.Name); + Assert.AreEqual(complete ? "changed" : "name", x.Name()); } } } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 206660b904..81319a619c 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -7,6 +7,8 @@ namespace Umbraco.Tests.TestHelpers.Stubs { internal class TestPublishedContent : PublishedElement, IPublishedContent { + private readonly Dictionary _names = new Dictionary(); + public TestPublishedContent(IPublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) : base(contentType, key, values, previewing) { @@ -17,7 +19,8 @@ namespace Umbraco.Tests.TestHelpers.Stubs public int Id { get; } public int? TemplateId { get; set; } public int SortOrder { get; set; } - public string Name { get; set; } + public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; + public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public IVariationContextAccessor VariationContextAccessor { get; set; } public PublishedCultureInfo GetCulture(string culture = null) { diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index ed737e7749..b252f51fae 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -71,7 +71,7 @@ namespace Umbraco.Web.Editors QueryExpression = queryExpression.ToString(), ResultCount = results.Count, ExecutionTime = timer.ElapsedMilliseconds, - SampleResults = results.Take(20).Select(x => new TemplateQueryResult { Icon = "icon-file", Name = x.Name }) + SampleResults = results.Take(20).Select(x => new TemplateQueryResult { Icon = "icon-file", Name = x.Name() }) }; } @@ -186,12 +186,12 @@ namespace Umbraco.Web.Editors : contents.OrderByDescending(x => x.UpdateDate); case "name": return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Name) - : contents.OrderByDescending(x => x.Name); + ? contents.OrderBy(x => x.Name()) + : contents.OrderByDescending(x => x.Name()); default: return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Name) - : contents.OrderByDescending(x => x.Name); + ? contents.OrderBy(x => x.Name()) + : contents.OrderByDescending(x => x.Name()); } } diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 978bbbcecb..f516aca080 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.Macros throw new ArgumentException("Document request has no node.", nameof(frequest)); PopulatePageData(frequest.PublishedContent.Id, - frequest.PublishedContent.Name, frequest.PublishedContent.ContentType.Id, frequest.PublishedContent.ContentType.Alias, + frequest.PublishedContent.Name(), frequest.PublishedContent.ContentType.Id, frequest.PublishedContent.ContentType.Alias, frequest.PublishedContent.WriterName, frequest.PublishedContent.CreatorName, frequest.PublishedContent.CreateDate, frequest.PublishedContent.UpdateDate, frequest.PublishedContent.Path, frequest.PublishedContent.Parent?.Id ?? -1); @@ -57,7 +57,7 @@ namespace Umbraco.Web.Macros if (doc == null) throw new ArgumentNullException(nameof(doc)); PopulatePageData(doc.Id, - doc.Name, doc.ContentType.Id, doc.ContentType.Alias, + doc.Name(), doc.ContentType.Id, doc.ContentType.Alias, doc.WriterName, doc.CreatorName, doc.CreateDate, doc.UpdateDate, doc.Path, doc.Parent?.Id ?? -1); @@ -228,7 +228,7 @@ namespace Umbraco.Web.Macros public int SortOrder => _inner.SortOrder; - public string Name => _inner.Name; + public string Name(string culture = null) => _inner.GetCultureName(culture); public PublishedCultureInfo GetCulture(string culture = null) { diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index d62b8c6665..04b5a64d07 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -41,7 +41,7 @@ namespace Umbraco.Web.Models public abstract int Id { get; } /// - public abstract string Name { get; } + public abstract string Name(string culture = null); /// public abstract string UrlSegment { get; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs index 51badc8b9a..bd6ad97d32 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.Navigable var i = 0; _builtInValues = new [] { - XmlString(i++, _content.Name), + XmlString(i++, _content.Name()), XmlString(i++, _content.ParentId), XmlString(i++, _content.CreateDate), XmlString(i++, _content.UpdateDate), diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 5712d55973..f208061c18 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -185,19 +185,18 @@ namespace Umbraco.Web.PublishedCache.NuCache public override int Id => _contentNode.Id; /// - public override string Name + public override string Name(string culture = null) { - get - { - if (!ContentType.VariesByCulture()) - return ContentData.Name; + // handle context culture + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - var culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - if (culture == "") - return ContentData.Name; + // invariant culture + if (culture == "") + return ContentType.VariesByCulture() ? null : ContentData.Name; - return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.Name : null; - } + // explicit culture + return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.Name : null; } /// diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index d954411f5e..d87d75059e 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -136,7 +136,11 @@ namespace Umbraco.Web.PublishedCache public override int SortOrder => 0; - public override string Name => _member.Name; + public override string Name(string culture = null) + { + // member name does not vary, ignore culture + return _member.Name; + } public override PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 54afb7abbd..29f32c1316 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1077,7 +1077,7 @@ namespace Umbraco.Web var standardVals = new Dictionary { { "Id", n.Id }, - { "NodeName", n.Name }, + { "NodeName", n.Name() }, { "NodeTypeAlias", n.ContentType.Alias }, { "CreateDate", n.CreateDate }, { "UpdateDate", n.UpdateDate }, diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 2a840828b6..0a4f033bd2 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -187,7 +187,7 @@ namespace Umbraco.Web.Routing var l = new List(); while (o != null) { - l.Add(o.Name); + l.Add(o.Name()); o = o.Parent; } l.Reverse(); From f99302ac6322936bc11754771d958726aa4a5e0f Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Apr 2019 17:10:25 +0200 Subject: [PATCH 006/218] Cleanup IPublishedContent/PropertyType --- .../PublishedContent/PublishedContentType.cs | 10 ++++--- .../PublishedContentTypeFactory.cs | 26 +++++++++---------- .../PublishedContent/PublishedPropertyType.cs | 2 -- .../Published/ConvertersTests.cs | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index fedd7445b7..3b03cfc9ea 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -34,12 +34,11 @@ namespace Umbraco.Core.Models.PublishedContent InitializeIndexes(); } - // fixme should be internal? /// /// This constructor is for tests and is not intended to be used directly from application code. /// /// - /// Values are assumed to be consisted and are not checked. + /// Values are assumed to be consistent and are not checked. /// public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) : this (id, alias, itemType, compositionAliases, variations, isElement) @@ -52,7 +51,12 @@ namespace Umbraco.Core.Models.PublishedContent InitializeIndexes(); } - // fixme + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) : this(id, alias, itemType, compositionAliases, variations, isElement) { diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 78d05607b4..17a15a2536 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -31,23 +31,19 @@ namespace Umbraco.Core.Models.PublishedContent return new PublishedContentType(contentType, this); } - // fixme kill - // for tests - internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) - { - return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); - } + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. internal IPublishedContentType CreateContentType(int id, string alias, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); } - // fixme kill - // for tests - internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) - { - return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); - } + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); @@ -65,8 +61,10 @@ namespace Umbraco.Core.Models.PublishedContent return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this); } - // fixme kill - // for tests + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. internal IPublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.Nothing) { return new PublishedPropertyType(propertyTypeAlias, dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 1632a85f44..0c2e62770e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -36,7 +36,6 @@ namespace Umbraco.Core.Models.PublishedContent ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); } - // fixme should be internal? /// /// This constructor is for tests and is not intended to be used directly from application code. /// @@ -50,7 +49,6 @@ namespace Umbraco.Core.Models.PublishedContent ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); } - // fixme should be internal? /// /// This constructor is for tests and is not intended to be used directly from application code. /// diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index f753acf82d..7ad81922e2 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -126,7 +126,7 @@ namespace Umbraco.Tests.Published var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); - var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", Array.Empty()); + var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", t => Enumerable.Empty()); var cnt1 = new TestPublishedContent(cntType1, 1234, Guid.NewGuid(), new Dictionary(), false); cacheContent[cnt1.Id] = cnt1; From 99319e0b493aa4e06a51f21321a251e8f8110445 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 22 Apr 2019 08:56:28 +0200 Subject: [PATCH 007/218] UmbracoContext: rename properties --- src/Umbraco.Web/UmbracoContext.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 8ab6f8c946..d6df5480cd 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -113,13 +113,30 @@ namespace Umbraco.Web /// /// Gets the published content cache. /// + [Obsolete("Use the Content property.")] public IPublishedContentCache ContentCache => PublishedSnapshot.Content; + /// + /// Gets the published content cache. + /// + public IPublishedContentCache Content => PublishedSnapshot.Content; + /// /// Gets the published media cache. /// + [Obsolete("Use the Media property.")] public IPublishedMediaCache MediaCache => PublishedSnapshot.Media; + /// + /// Gets the published media cache. + /// + public IPublishedMediaCache Media => PublishedSnapshot.Media; + + /// + /// Gets the domains cache. + /// + public IDomainCache Domains => PublishedSnapshot.Domains; + /// /// Boolean value indicating whether the current request is a front-end umbraco request /// @@ -191,6 +208,8 @@ namespace Umbraco.Web #region Urls + // fixme do something with these + /// /// Gets the url of a content identified by its identifier. /// From 2b54cc50ab4c80fa75f6b7f9dbd1ebb4f060970b Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Apr 2019 18:32:33 +0200 Subject: [PATCH 008/218] Refactor IPublishedContent.UrlSegment() --- .../PublishedContent/IPublishedContent.cs | 10 +++----- .../PublishedContentWrapped.cs | 2 +- .../PublishedContent/PublishedCultureInfos.cs | 2 +- .../PublishedMediaCacheTests.cs | 2 +- .../DictionaryPublishedContent.cs | 2 +- .../PublishedContentCache.cs | 2 +- .../XmlPublishedContent.cs | 9 +++---- .../Published/NestedContentTests.cs | 2 +- .../PublishedContentDataTableTests.cs | 6 +++-- .../PublishedContentLanguageVariantTests.cs | 6 ++--- .../PublishedContentMoreTests.cs | 6 ++--- .../SolidPublishedSnapshot.cs | 4 +++- .../TestHelpers/Stubs/TestPublishedContent.cs | 4 +++- .../PublishedContentHashtableConverter.cs | 2 +- .../Models/PublishedContentBase.cs | 2 +- .../PublishedCache/NuCache/ContentCache.cs | 12 +++++----- .../NuCache/Navigable/NavigableContent.cs | 2 +- .../NuCache/PublishedContent.cs | 19 +++++++-------- .../PublishedCache/PublishedMember.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 24 ------------------- 20 files changed, 47 insertions(+), 73 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 95b943d751..304066196d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -29,18 +29,14 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the name of the content item. /// - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The name of the content. + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). string Name(string culture = null); /// /// Gets the url segment of the content item. /// - /// - /// The value of this property is contextual. When the content type is multi-lingual, - /// this is the name for the 'current' culture. Otherwise, it is the invariant url segment. - /// - string UrlSegment { get; } + /// The specific culture to get the url segment for. If null is used the current culture is used (Default is null). + string UrlSegment(string culture = null); /// /// Gets the sort order of the content item. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 753f75c3cb..16732840ea 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -61,7 +61,7 @@ namespace Umbraco.Core.Models.PublishedContent public virtual string Name(string culture = null) => _content.Name(culture); /// - public virtual string UrlSegment => _content.UrlSegment; + public virtual string UrlSegment(string culture = null) => _content.UrlSegment(culture); /// public virtual int SortOrder => _content.SortOrder; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index 9c5977e8a6..eedc97dbfc 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the url segment of the item. /// - public string UrlSegment { get; } + internal string UrlSegment { get; } /// /// Gets the date associated with the culture. diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index 8dd5cf3890..2245f600dc 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -397,7 +397,7 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(keyVal, doc.Key); Assert.AreEqual(templateIdVal, doc.TemplateId); Assert.AreEqual(sortOrderVal, doc.SortOrder); - Assert.AreEqual(urlNameVal, doc.UrlSegment); + Assert.AreEqual(urlNameVal, doc.UrlSegment()); Assert.AreEqual(nodeTypeAliasVal, doc.ContentType.Alias); Assert.AreEqual(nodeTypeIdVal, doc.ContentType.Id); Assert.AreEqual(writerNameVal, doc.WriterName); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index c6c16a2466..9e615e6745 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -163,7 +163,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); public override IReadOnlyDictionary Cultures => NoCultures.Value; - public override string UrlSegment => _urlName; + public override string UrlSegment(string culture = null) => _urlName; public override string WriterName => _creatorName; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index d69799dfdf..1ad6e045c6 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -271,7 +271,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache while (hasDomains == false && n != null) // n is null at root { // get the url - var urlName = n.UrlSegment; + var urlName = n.UrlSegment(); pathParts.Add(urlName); // move to parent node diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index c547512ba8..8f30bdd789 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -212,13 +212,10 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } - public override string UrlSegment + public override string UrlSegment(string culture = null) { - get - { - EnsureNodeInitialized(); - return _urlName; - } + EnsureNodeInitialized(); + return _urlName; } public override int Level diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 35288c1f75..1cf291b263 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -287,7 +287,7 @@ namespace Umbraco.Tests.Published public override string Name(string culture = null) => default; public override PublishedCultureInfo GetCulture(string culture = ".") => throw new NotSupportedException(); public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public override string UrlSegment { get; } + public override string UrlSegment(string culture = null) => default; public override string WriterName { get; } public override string CreatorName { get; } public override int WriterId { get; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 220909e237..7d1ef25dcf 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -139,7 +139,6 @@ namespace Umbraco.Tests.PublishedContent TemplateId = 5, UpdateDate = DateTime.Now, Path = "-1,3", - UrlSegment = "home-page", Version = Guid.NewGuid(), WriterId = 1, WriterName = "Shannon", @@ -148,6 +147,7 @@ namespace Umbraco.Tests.PublishedContent Children = new List() }; d.SetName("Page" + Guid.NewGuid()); + d.SetUrlSegment("home-page"); d.Properties = new Collection(new List { new RawValueProperty(factory.CreatePropertyType("property1", 1), d, "value" + indexVals), @@ -184,6 +184,7 @@ namespace Umbraco.Tests.PublishedContent private class TestPublishedContent : IPublishedContent { private readonly Dictionary _names = new Dictionary(); + private readonly Dictionary _urlSegments = new Dictionary(); public string Url { get; set; } public string GetUrl(string culture = null) => throw new NotSupportedException(); @@ -209,7 +210,8 @@ namespace Umbraco.Tests.PublishedContent public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); public IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public string UrlSegment { get; set; } + public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; + public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string WriterName { get; set; } public string CreatorName { get; set; } public int WriterId { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 9cec962a38..bf84413fae 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -122,7 +122,6 @@ namespace Umbraco.Tests.PublishedContent { Id = 1, SortOrder = 0, - UrlSegment = "content-1", Path = "/1", Level = 1, Url = "/content-1", @@ -134,12 +133,12 @@ namespace Umbraco.Tests.PublishedContent } }; item1.SetName("Content 1"); + item1.SetUrlSegment("content-1"); var item2 = new SolidPublishedContent(contentType1) { Id = 2, SortOrder = 0, - UrlSegment = "content-2", Path = "/1/2", Level = 2, Url = "/content-1/content-2", @@ -151,6 +150,7 @@ namespace Umbraco.Tests.PublishedContent } }; item2.SetName("Content 2"); + item2.SetUrlSegment("content-2"); var prop4 = new SolidPublishedPropertyWithLanguageVariants { @@ -164,7 +164,6 @@ namespace Umbraco.Tests.PublishedContent { Id = 3, SortOrder = 0, - UrlSegment = "content-3", Path = "/1/2/3", Level = 3, Url = "/content-1/content-2/content-3", @@ -176,6 +175,7 @@ namespace Umbraco.Tests.PublishedContent } }; item3.SetName("Content 3"); + item3.SetUrlSegment("content-3"); item1.Children = new List { item2 }; item2.Parent = item1; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index ffe88f5d13..9f9f3b8d41 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -29,7 +29,6 @@ namespace Umbraco.Tests.PublishedContent { Id = 1, SortOrder = 0, - UrlSegment = "content-1", Path = "/1", Level = 1, Url = "/content-1", @@ -47,13 +46,13 @@ namespace Umbraco.Tests.PublishedContent } }; content.SetName("Content 1"); + content.SetUrlSegment("content-1"); cache.Add(content); content = new SolidPublishedContent(contentType2) { Id = 2, SortOrder = 1, - UrlSegment = "content-2", Path = "/2", Level = 1, Url = "/content-2", @@ -71,13 +70,13 @@ namespace Umbraco.Tests.PublishedContent } }; content.SetName("Content 2"); + content.SetUrlSegment("content-2"); cache.Add(content); content = new SolidPublishedContent(contentType2Sub) { Id = 3, SortOrder = 2, - UrlSegment = "content-2sub", Path = "/3", Level = 1, Url = "/content-2sub", @@ -95,6 +94,7 @@ namespace Umbraco.Tests.PublishedContent } }; content.SetName("Content 2Sub"); + content.SetUrlSegment("content-2sub"); cache.Add(content); } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index a25faea07a..e1c8cda3a9 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -156,6 +156,7 @@ namespace Umbraco.Tests.PublishedContent internal class SolidPublishedContent : IPublishedContent { private readonly Dictionary _names = new Dictionary(); + private readonly Dictionary _urlSegments = new Dictionary(); #region Constructor @@ -183,7 +184,8 @@ namespace Umbraco.Tests.PublishedContent public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); public IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public string UrlSegment { get; set; } + public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; + public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string WriterName { get; set; } public string CreatorName { get; set; } public int WriterId { get; set; } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 81319a619c..d621197bf8 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -8,6 +8,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs internal class TestPublishedContent : PublishedElement, IPublishedContent { private readonly Dictionary _names = new Dictionary(); + private readonly Dictionary _urlSegments = new Dictionary(); public TestPublishedContent(IPublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) : base(contentType, key, values, previewing) @@ -35,7 +36,8 @@ namespace Umbraco.Tests.TestHelpers.Stubs return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos : null; } public IReadOnlyDictionary Cultures { get; set; } - public string UrlSegment { get; set; } + public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; + public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string DocumentTypeAlias => ContentType.Alias; public int DocumentTypeId { get; set; } public string WriterName { get; set; } diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index f516aca080..977cd6fdc9 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -259,7 +259,7 @@ namespace Umbraco.Web.Macros } } - public string UrlSegment => throw new NotImplementedException(); + public string UrlSegment(string culture = null) => throw new NotImplementedException(); public string WriterName { get; } diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 04b5a64d07..f93a6d08c4 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Models public abstract string Name(string culture = null); /// - public abstract string UrlSegment { get; } + public abstract string UrlSegment(string culture = null); /// public abstract int SortOrder { get; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index d070b959ed..08664f0a7a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -109,8 +109,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // hideTopLevelNode = support legacy stuff, look for /*/path/to/node // else normal, look for /path/to/node content = hideTopLevelNode.Value - ? GetAtRoot(preview).SelectMany(x => x.Children).FirstOrDefault(x => x.GetUrlSegment(culture) == parts[0]) - : GetAtRoot(preview).FirstOrDefault(x => x.GetUrlSegment(culture) == parts[0]); + ? GetAtRoot(preview).SelectMany(x => x.Children).FirstOrDefault(x => x.UrlSegment(culture) == parts[0]) + : GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(culture) == parts[0]); content = FollowRoute(content, parts, 1, culture); } @@ -119,7 +119,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // have to look for /foo (see note in ApplyHideTopLevelNodeFromPath). if (content == null && hideTopLevelNode.Value && parts.Length == 1) { - content = GetAtRoot(preview).FirstOrDefault(x => x.GetUrlSegment(culture) == parts[0]); + content = GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(culture) == parts[0]); } return content; @@ -149,7 +149,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // or we reach the content root, collecting urls in the way var pathParts = new List(); var n = node; - var urlSegment = n.GetUrlSegment(culture); + var urlSegment = n.UrlSegment(culture); var hasDomains = _domainHelper.NodeHasDomains(n.Id); while (hasDomains == false && n != null) // n is null at root { @@ -161,7 +161,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // move to parent node n = n.Parent; if (n != null) - urlSegment = n.GetUrlSegment(culture); + urlSegment = n.UrlSegment(culture); hasDomains = n != null && _domainHelper.NodeHasDomains(n.Id); } @@ -191,7 +191,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var part = parts[i++]; content = content.Children.FirstOrDefault(x => { - var urlSegment = x.GetUrlSegment(culture); + var urlSegment = x.UrlSegment(culture); return urlSegment == part; }); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs index bd6ad97d32..c9dd493ee6 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.Navigable XmlString(i++, _content.TemplateId), XmlString(i++, _content.WriterId), XmlString(i++, _content.CreatorId), - XmlString(i++, _content.UrlSegment), + XmlString(i++, _content.UrlSegment()), XmlString(i, _content.IsDraft()) }; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index f208061c18..e51220dfab 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -200,19 +200,18 @@ namespace Umbraco.Web.PublishedCache.NuCache } /// - public override string UrlSegment + public override string UrlSegment(string culture = null) { - get - { - if (!ContentType.VariesByCulture()) - return _urlSegment; + // handle context culture + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - var culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - if (culture == "") - return _urlSegment; + // invariant culture + if (culture == "") + return ContentType.VariesByCulture() ? null : _urlSegment; - return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.UrlSegment : null; - } + // explicit culture + return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.UrlSegment : null; } /// diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index d87d75059e..733b4068e0 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -146,7 +146,7 @@ namespace Umbraco.Web.PublishedCache public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public override string UrlSegment => throw new NotSupportedException(); + public override string UrlSegment(string culture = null) => throw new NotSupportedException(); // TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current public override string WriterName => _member.GetCreatorProfile().Name; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 29f32c1316..2495415834 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -60,30 +60,6 @@ namespace Umbraco.Web } } - /// - /// Gets the Url segment. - /// - /// - /// Gets the url segment for the document, taking its content type and a specified - /// culture in account. For invariant content types, the culture is ignored, else it is - /// used to try and find the segment corresponding to the culture. May return null. - /// - public static string GetUrlSegment(this IPublishedContent content, string culture = null) - { - // for invariant content, return the invariant url segment - if (!content.ContentType.VariesByCulture()) - return content.UrlSegment; - - // content.GetCulture(culture) will use the 'current' culture (via accessor) in case 'culture' - // is null (meaning, 'current') - and can return 'null' if that culture is not published - and - // will return 'null' if the content is variant and culture is invariant - - // else try and get the culture info - // return the corresponding url segment, or null if none - var cultureInfo = content.GetCulture(culture); - return cultureInfo?.UrlSegment; - } - public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) { if (Current.Configs.Settings().WebRouting.DisableAlternativeTemplates) From 11ef00c63d9773fccf1d58897a74f7808c428760 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Apr 2019 20:03:07 +0200 Subject: [PATCH 009/218] Refactor IPublishedContent.Url() --- .../PublishedContent/IPublishedContent.cs | 12 +-- .../PublishedContentWrapped.cs | 5 +- .../Models/PublishedContent/UrlMode.cs} | 10 +-- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../PublishedContentDataTableTests.cs | 3 +- .../PublishedContentLanguageVariantTests.cs | 6 +- .../PublishedContentMoreTests.cs | 6 +- .../SolidPublishedSnapshot.cs | 5 +- src/Umbraco.Tests/Routing/UrlProviderTests.cs | 4 +- .../Routing/UrlsProviderWithDomainsTests.cs | 3 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 6 +- .../TestControllerActivatorBase.cs | 2 +- .../TestHelpers/Stubs/TestPublishedContent.cs | 3 +- .../TestHelpers/TestObjects-Mocks.cs | 3 +- .../Testing/TestingTests/MockTests.cs | 2 +- .../Web/TemplateUtilitiesTests.cs | 4 +- .../Controllers/UmbLoginController.cs | 2 +- .../PublishedContentHashtableConverter.cs | 4 +- .../Models/PublishedContentBase.cs | 10 +-- .../MultiUrlPickerValueEditor.cs | 4 +- .../MultiUrlPickerValueConverter.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 82 +++++-------------- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 2 +- .../Routing/ContentFinderByRedirectUrl.cs | 2 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 24 +++--- src/Umbraco.Web/Routing/IUrlProvider.cs | 2 +- src/Umbraco.Web/Routing/UrlProvider.cs | 16 ++-- .../Templates/TemplateUtilities.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - src/Umbraco.Web/UmbracoContext.cs | 8 +- src/Umbraco.Web/UrlHelperRenderExtensions.cs | 2 +- 31 files changed, 91 insertions(+), 147 deletions(-) rename src/{Umbraco.Web/Routing/UrlProviderMode.cs => Umbraco.Core/Models/PublishedContent/UrlMode.cs} (63%) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 304066196d..04a33fc0bc 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -93,16 +93,6 @@ namespace Umbraco.Core.Models.PublishedContent /// DateTime UpdateDate { get; } - /// - /// Gets the url of the content item. - /// - /// - /// The value of this property is contextual. It depends on the 'current' request uri, - /// if any. In addition, when the content type is multi-lingual, this is the url for the - /// 'current' culture. Otherwise, it is the invariant url. - /// - string Url { get; } - /// /// Gets the url of the content item. /// @@ -111,7 +101,7 @@ namespace Umbraco.Core.Models.PublishedContent /// if any. In addition, when the content type is multi-lingual, this is the url for the /// specified culture. Otherwise, it is the invariant url. /// - string GetUrl(string culture = null); + string Url(string culture = null, UrlMode mode = UrlMode.Auto); /// /// Gets culture infos for a culture. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 16732840ea..ee7ed31d1d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -94,10 +94,7 @@ namespace Umbraco.Core.Models.PublishedContent public virtual DateTime UpdateDate => _content.UpdateDate; /// - public virtual string Url => _content.Url; - - /// - public virtual string GetUrl(string culture = null) => _content.GetUrl(culture); + public virtual string Url(string culture = null, UrlMode mode = UrlMode.Auto) => _content.Url(culture, mode); /// public PublishedCultureInfo GetCulture(string culture = null) => _content.GetCulture(culture); diff --git a/src/Umbraco.Web/Routing/UrlProviderMode.cs b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs similarity index 63% rename from src/Umbraco.Web/Routing/UrlProviderMode.cs rename to src/Umbraco.Core/Models/PublishedContent/UrlMode.cs index ac29aed3fb..f19f93bec1 100644 --- a/src/Umbraco.Web/Routing/UrlProviderMode.cs +++ b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs @@ -1,13 +1,9 @@ -namespace Umbraco.Web.Routing +namespace Umbraco.Core.Models.PublishedContent { /// - /// Specifies the type of urls that the url provider should produce, Auto is the default + /// Specifies the type of urls that the url provider should produce, Auto is the default. /// - /// - /// The Relative option can lead to invalid results when combined with hostnames, but it is the only way to reproduce - /// the true, pre-4.10, always-relative behavior of Umbraco. - /// - public enum UrlProviderMode + public enum UrlMode { /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 20cc089d1d..d0936fd579 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -223,6 +223,7 @@ + diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 7d1ef25dcf..69eca7ef88 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -186,8 +186,7 @@ namespace Umbraco.Tests.PublishedContent private readonly Dictionary _names = new Dictionary(); private readonly Dictionary _urlSegments = new Dictionary(); - public string Url { get; set; } - public string GetUrl(string culture = null) => throw new NotSupportedException(); + public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => default; public PublishedItemType ItemType { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index bf84413fae..f978c8501b 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -124,7 +124,6 @@ namespace Umbraco.Tests.PublishedContent SortOrder = 0, Path = "/1", Level = 1, - Url = "/content-1", ParentId = -1, ChildIds = new[] { 2 }, Properties = new Collection @@ -134,6 +133,7 @@ namespace Umbraco.Tests.PublishedContent }; item1.SetName("Content 1"); item1.SetUrlSegment("content-1"); + item1.SetUrl("/content-1"); var item2 = new SolidPublishedContent(contentType1) { @@ -141,7 +141,6 @@ namespace Umbraco.Tests.PublishedContent SortOrder = 0, Path = "/1/2", Level = 2, - Url = "/content-1/content-2", ParentId = 1, ChildIds = new int[] { 3 }, Properties = new Collection @@ -151,6 +150,7 @@ namespace Umbraco.Tests.PublishedContent }; item2.SetName("Content 2"); item2.SetUrlSegment("content-2"); + item2.SetUrl("/content-1/content-2"); var prop4 = new SolidPublishedPropertyWithLanguageVariants { @@ -166,7 +166,6 @@ namespace Umbraco.Tests.PublishedContent SortOrder = 0, Path = "/1/2/3", Level = 3, - Url = "/content-1/content-2/content-3", ParentId = 2, ChildIds = new int[] { }, Properties = new Collection @@ -176,6 +175,7 @@ namespace Umbraco.Tests.PublishedContent }; item3.SetName("Content 3"); item3.SetUrlSegment("content-3"); + item3.SetUrl("/content-1/content-2/content-3"); item1.Children = new List { item2 }; item2.Parent = item1; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 9f9f3b8d41..378671b81b 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -31,7 +31,6 @@ namespace Umbraco.Tests.PublishedContent SortOrder = 0, Path = "/1", Level = 1, - Url = "/content-1", ParentId = -1, ChildIds = new int[] { }, Properties = new Collection @@ -47,6 +46,7 @@ namespace Umbraco.Tests.PublishedContent }; content.SetName("Content 1"); content.SetUrlSegment("content-1"); + content.SetUrl("/content-1"); cache.Add(content); content = new SolidPublishedContent(contentType2) @@ -55,7 +55,6 @@ namespace Umbraco.Tests.PublishedContent SortOrder = 1, Path = "/2", Level = 1, - Url = "/content-2", ParentId = -1, ChildIds = new int[] { }, Properties = new Collection @@ -71,6 +70,7 @@ namespace Umbraco.Tests.PublishedContent }; content.SetName("Content 2"); content.SetUrlSegment("content-2"); + content.SetUrl("/content-2"); cache.Add(content); content = new SolidPublishedContent(contentType2Sub) @@ -79,7 +79,6 @@ namespace Umbraco.Tests.PublishedContent SortOrder = 2, Path = "/3", Level = 1, - Url = "/content-2sub", ParentId = -1, ChildIds = new int[] { }, Properties = new Collection @@ -95,6 +94,7 @@ namespace Umbraco.Tests.PublishedContent }; content.SetName("Content 2Sub"); content.SetUrlSegment("content-2sub"); + content.SetUrl("/content-2sub"); cache.Add(content); } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index e1c8cda3a9..91fd1efb92 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -157,6 +157,7 @@ namespace Umbraco.Tests.PublishedContent { private readonly Dictionary _names = new Dictionary(); private readonly Dictionary _urlSegments = new Dictionary(); + private readonly Dictionary _urls = new Dictionary(); #region Constructor @@ -195,8 +196,8 @@ namespace Umbraco.Tests.PublishedContent public DateTime UpdateDate { get; set; } public Guid Version { get; set; } public int Level { get; set; } - public string Url { get; set; } - public string GetUrl(string culture = null) => throw new NotSupportedException(); + public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => _urls.TryGetValue(culture ?? "", out var url) ? url : null; + public void SetUrl(string url, string culture = null) => _urls[culture ?? ""] = url; public PublishedItemType ItemType { get { return PublishedItemType.Content; } } public bool IsDraft(string culture = null) => false; diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 236c198b3a..38ef983120 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -314,7 +314,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("/home/sub1/custom-sub-1/", umbracoContext.UrlProvider.GetUrl(1177)); - umbracoContext.UrlProvider.Mode = UrlProviderMode.Absolute; + umbracoContext.UrlProvider.Mode = UrlMode.Absolute; Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", umbracoContext.UrlProvider.GetUrl(1177)); } @@ -335,7 +335,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("#", umbracoContext.UrlProvider.GetUrl(999999)); - umbracoContext.UrlProvider.Mode = UrlProviderMode.Absolute; + umbracoContext.UrlProvider.Mode = UrlMode.Absolute; Assert.AreEqual("#", umbracoContext.UrlProvider.GetUrl(999999)); } diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index c01ee83d6a..5a20d4da31 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.TestHelpers; @@ -359,7 +360,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("/en/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111)); Assert.AreEqual("http://domain3.com/en/1003-1-1/", umbracoContext.UrlProvider.GetUrl(100311)); - umbracoContext.UrlProvider.Mode = UrlProviderMode.Absolute; + umbracoContext.UrlProvider.Mode = UrlMode.Absolute; Assert.AreEqual("http://domain1.com/en/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111)); Assert.AreEqual("http://domain3.com/en/1003-1-1/", umbracoContext.UrlProvider.GetUrl(100311)); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index e07ae576e6..9f57109329 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -194,7 +194,7 @@ namespace Umbraco.Tests.Runtimes Assert.IsTrue(pcontent.IsDraft()); // no published url - Assert.AreEqual("#", pcontent.GetUrl()); + Assert.AreEqual("#", pcontent.Url()); // now publish the document + make some unpublished changes contentService.SaveAndPublish(content); @@ -208,7 +208,7 @@ namespace Umbraco.Tests.Runtimes Assert.IsFalse(pcontent.IsDraft()); // but the url is the published one - no draft url - Assert.AreEqual("/test/", pcontent.GetUrl()); + Assert.AreEqual("/test/", pcontent.Url()); // and also an updated draft document pcontent = umbracoContext.ContentCache.GetById(true, content.Id); @@ -217,7 +217,7 @@ namespace Umbraco.Tests.Runtimes Assert.IsTrue(pcontent.IsDraft()); // and the published document has a url - Assert.AreEqual("/test/", pcontent.GetUrl()); + Assert.AreEqual("/test/", pcontent.Url()); umbracoContextReference.Dispose(); mainDom.Stop(); diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index c6bbebf550..cf2b3b843f 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -146,7 +146,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting umbracoContextAccessor.UmbracoContext = umbCtx; var urlHelper = new Mock(); - urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); var membershipHelper = new MembershipHelper(umbCtx.HttpContext, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index d621197bf8..4262851fc9 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -49,8 +49,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs public DateTime UpdateDate { get; set; } public Guid Version { get; set; } public int Level { get; set; } - public string Url { get; set; } - public string GetUrl(string culture = null) => throw new NotSupportedException(); + public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => throw new NotSupportedException(); public PublishedItemType ItemType => ContentType.ItemType; public bool IsDraft(string culture = null) => false; public bool IsPublished(string culture = null) => true; diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 75e9cd60cb..ee938cd027 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; @@ -145,7 +146,7 @@ namespace Umbraco.Tests.TestHelpers var umbracoSettingsMock = new Mock(); var webRoutingSectionMock = new Mock(); - webRoutingSectionMock.Setup(x => x.UrlProviderMode).Returns(UrlProviderMode.Auto.ToString()); + webRoutingSectionMock.Setup(x => x.UrlProviderMode).Returns(UrlMode.Auto.ToString()); umbracoSettingsMock.Setup(x => x.WebRouting).Returns(webRoutingSectionMock.Object); return umbracoSettingsMock.Object; } diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 57381eb287..f0e31226e2 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -76,7 +76,7 @@ namespace Umbraco.Tests.Testing.TestingTests var umbracoContext = TestObjects.GetUmbracoContextMock(); var urlProviderMock = new Mock(); - urlProviderMock.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + urlProviderMock.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); var urlProvider = urlProviderMock.Object; diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 5e9208faf9..c868914347 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -80,8 +80,8 @@ namespace Umbraco.Tests.Web //setup a mock url provider which we'll use fo rtesting var testUrlProvider = new Mock(); testUrlProvider - .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlProviderMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); + .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index 2980b4a4c0..02130da8b5 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.Controllers // if it's not a local url we'll redirect to the root of the current site return Redirect(Url.IsLocalUrl(model.RedirectUrl) ? model.RedirectUrl - : CurrentPage.AncestorOrSelf(1).Url); + : CurrentPage.AncestorOrSelf(1).Url()); } //redirect to current page by default diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 977cd6fdc9..1fafefdcc5 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -277,9 +277,7 @@ namespace Umbraco.Web.Macros public int Level => _inner.Level; - public string Url => throw new NotImplementedException(); - - public string GetUrl(string culture = null) => throw new NotSupportedException(); + public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => throw new NotSupportedException(); public PublishedItemType ItemType => PublishedItemType.Content; diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index f93a6d08c4..e46bb8fddd 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -76,15 +76,12 @@ namespace Umbraco.Web.Models /// public abstract DateTime UpdateDate { get; } - /// - public virtual string Url => GetUrl(); - /// /// /// The url of documents are computed by the document url providers. The url of medias are, at the moment, /// computed here from the 'umbracoFile' property -- but we should move to media url providers at some point. /// - public virtual string GetUrl(string culture = null) // TODO: consider .GetCulture("fr-FR").Url + public virtual string Url(string culture = null, UrlMode mode = UrlMode.Auto) { switch (ItemType) { @@ -96,9 +93,12 @@ namespace Umbraco.Web.Models if (umbracoContext.UrlProvider == null) throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext.UrlProvider is null."); - return umbracoContext.UrlProvider.GetUrl(this, culture); + return umbracoContext.UrlProvider.GetUrl(this, mode, culture); case PublishedItemType.Media: + if (mode == UrlMode.Absolute) + throw new NotSupportedException("Absolute urls are not supported for media items."); + var prop = GetProperty(Constants.Conventions.Media.File); if (prop?.GetValue() == null) { diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index 1cb590d43c..ac0fb375c7 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -82,7 +82,7 @@ namespace Umbraco.Web.PropertyEditors icon = documentEntity.ContentTypeIcon; published = culture == null ? documentEntity.Published : documentEntity.PublishedCultures.Contains(culture); udi = new GuidUdi(Constants.UdiEntityType.Document, documentEntity.Key); - url = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(entity.Key)?.Url ?? "#"; + url = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(entity.Key)?.Url() ?? "#"; trashed = documentEntity.Trashed; } else if(entity is IContentEntitySlim contentEntity) @@ -91,7 +91,7 @@ namespace Umbraco.Web.PropertyEditors isMedia = true; published = !contentEntity.Trashed; udi = new GuidUdi(Constants.UdiEntityType.Media, contentEntity.Key); - url = _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(entity.Key)?.Url ?? "#"; + url = _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(entity.Key)?.Url() ?? "#"; trashed = contentEntity.Trashed; } else diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs index 87ab5b8ff9..8e9ee9d6f8 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { continue; } - url = content.Url; + url = content.Url(); } links.Add( diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 2495415834..7776a7c8ac 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -24,65 +24,6 @@ namespace Umbraco.Web private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback; private static IPublishedSnapshot PublishedSnapshot => Current.PublishedSnapshot; - #region Urls - - /// - /// Gets the url for the content. - /// - /// The content. - /// The url for the content. - /// Better use the Url property but that method is here to complement UrlAbsolute(). - public static string Url(this IPublishedContent content) - { - return content.Url; - } - - /// - /// Gets the absolute url for the content. - /// - /// The content. - /// The absolute url for the content. - public static string UrlAbsolute(this IPublishedContent content) - { - // adapted from PublishedContentBase.Url - switch (content.ItemType) - { - case PublishedItemType.Content: - if (Current.UmbracoContext == null) - throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null."); - if (Current.UmbracoContext.UrlProvider == null) - throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null."); - return Current.UmbracoContext.UrlProvider.GetUrl(content.Id, true); - case PublishedItemType.Media: - throw new NotSupportedException("AbsoluteUrl is not supported for media types."); - default: - throw new ArgumentOutOfRangeException(); - } - } - - public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) - { - if (Current.Configs.Settings().WebRouting.DisableAlternativeTemplates) - return content.TemplateId == templateId; - - if (content.TemplateId == templateId || !Current.Configs.Settings().WebRouting.ValidateAlternativeTemplates) - return true; - - var publishedContentContentType = Current.Services.ContentTypeService.Get(content.ContentType.Id); - if (publishedContentContentType == null) - throw new NullReferenceException("No content type returned for published content (contentType='" + content.ContentType.Id + "')"); - - return publishedContentContentType.IsAllowedTemplate(templateId); - - } - public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) - { - var template = Current.Services.FileService.GetTemplate(templateAlias); - return template != null && content.IsAllowedTemplate(template.Id); - } - - #endregion - #region IsComposedOf /// @@ -116,6 +57,27 @@ namespace Umbraco.Web return template == null ? string.Empty : template.Alias; } + public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) + { + if (Current.Configs.Settings().WebRouting.DisableAlternativeTemplates) + return content.TemplateId == templateId; + + if (content.TemplateId == templateId || !Current.Configs.Settings().WebRouting.ValidateAlternativeTemplates) + return true; + + var publishedContentContentType = Current.Services.ContentTypeService.Get(content.ContentType.Id); + if (publishedContentContentType == null) + throw new NullReferenceException("No content type returned for published content (contentType='" + content.ContentType.Id + "')"); + + return publishedContentContentType.IsAllowedTemplate(templateId); + + } + public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) + { + var template = Current.Services.FileService.GetTemplate(templateAlias); + return template != null && content.IsAllowedTemplate(template.Id); + } + #endregion #region HasValue, Value, Value @@ -1059,7 +1021,7 @@ namespace Umbraco.Web { "UpdateDate", n.UpdateDate }, { "CreatorName", n.CreatorName }, { "WriterName", n.WriterName }, - { "Url", n.Url } + { "Url", n.Url() } }; var userVals = new Dictionary(); diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 4c879e931f..bbe3a9db8c 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - public UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlMode mode, string culture, Uri current) { return null; // we have nothing to say } diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs index f20aa95c0d..eae2b57378 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.Routing } var content = frequest.UmbracoContext.ContentCache.GetById(redirectUrl.ContentId); - var url = content == null ? "#" : content.GetUrl(redirectUrl.Culture); + var url = content == null ? "#" : content.Url(redirectUrl.Culture); if (url.StartsWith("#")) { _logger.Debug("Route {Route} matches content {ContentId} which has no url.", route, redirectUrl.ContentId); diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 9e13d3b9c1..98ba743fe5 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - public virtual UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public virtual UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlMode mode, string culture, Uri current) { if (!current.IsAbsoluteUri) throw new ArgumentException("Current url must be absolute.", nameof(current)); @@ -39,7 +39,7 @@ namespace Umbraco.Web.Routing return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); } - internal UrlInfo GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode, string culture) + internal UrlInfo GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlMode mode, string culture) { if (string.IsNullOrWhiteSpace(route)) { @@ -121,7 +121,7 @@ namespace Umbraco.Web.Routing #region Utilities - Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlProviderMode mode) + Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode) { Uri uri; @@ -130,15 +130,15 @@ namespace Umbraco.Web.Routing if (domainUri == null) // no domain was found { if (current == null) - mode = UrlProviderMode.Relative; // best we can do + mode = UrlMode.Relative; // best we can do switch (mode) { - case UrlProviderMode.Absolute: + case UrlMode.Absolute: uri = new Uri(current.GetLeftPart(UriPartial.Authority) + path); break; - case UrlProviderMode.Relative: - case UrlProviderMode.Auto: + case UrlMode.Relative: + case UrlMode.Auto: uri = new Uri(path, UriKind.Relative); break; default: @@ -147,21 +147,21 @@ namespace Umbraco.Web.Routing } else // a domain was found { - if (mode == UrlProviderMode.Auto) + if (mode == UrlMode.Auto) { //this check is a little tricky, we can't just compare domains if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) - mode = UrlProviderMode.Relative; + mode = UrlMode.Relative; else - mode = UrlProviderMode.Absolute; + mode = UrlMode.Absolute; } switch (mode) { - case UrlProviderMode.Absolute: + case UrlMode.Absolute: uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); break; - case UrlProviderMode.Relative: + case UrlMode.Relative: uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); break; default: diff --git a/src/Umbraco.Web/Routing/IUrlProvider.cs b/src/Umbraco.Web/Routing/IUrlProvider.cs index 55d39880d6..854363f110 100644 --- a/src/Umbraco.Web/Routing/IUrlProvider.cs +++ b/src/Umbraco.Web/Routing/IUrlProvider.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it should return null. /// - UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current); + UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlMode mode, string culture, Uri current); /// /// Gets the other urls of a published content. diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index 0662d46f49..3e07c8020a 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -31,10 +31,10 @@ namespace Umbraco.Web.Routing _umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _urlProviders = urlProviders; _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); - var provider = UrlProviderMode.Auto; + var provider = UrlMode.Auto; Mode = provider; - if (Enum.TryParse(routingSettings.UrlProviderMode, out provider)) + if (Enum.TryParse(routingSettings.UrlProviderMode, out provider)) { Mode = provider; } @@ -47,7 +47,7 @@ namespace Umbraco.Web.Routing /// The list of url providers. /// The current variation accessor. /// An optional provider mode. - public UrlProvider(UmbracoContext umbracoContext, IEnumerable urlProviders, IVariationContextAccessor variationContextAccessor, UrlProviderMode mode = UrlProviderMode.Auto) + public UrlProvider(UmbracoContext umbracoContext, IEnumerable urlProviders, IVariationContextAccessor variationContextAccessor, UrlMode mode = UrlMode.Auto) { _umbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _urlProviders = urlProviders; @@ -63,13 +63,13 @@ namespace Umbraco.Web.Routing /// /// Gets or sets the provider url mode. /// - public UrlProviderMode Mode { get; set; } + public UrlMode Mode { get; set; } #endregion #region GetUrl - private UrlProviderMode GetMode(bool absolute) => absolute ? UrlProviderMode.Absolute : Mode; + private UrlMode GetMode(bool absolute) => absolute ? UrlMode.Absolute : Mode; private IPublishedContent GetDocument(int id) => _umbracoContext.ContentCache.GetById(id); private IPublishedContent GetDocument(Guid id) => _umbracoContext.ContentCache.GetById(id); @@ -131,7 +131,7 @@ namespace Umbraco.Web.Routing /// A culture. /// The current absolute url. /// The url for the published content. - public string GetUrl(Guid id, UrlProviderMode mode, string culture = null, Uri current = null) + public string GetUrl(Guid id, UrlMode mode, string culture = null, Uri current = null) => GetUrl(GetDocument(id), mode, culture, current); /// @@ -167,7 +167,7 @@ namespace Umbraco.Web.Routing /// A culture. /// The current absolute url. /// The url for the published content. - public string GetUrl(int id, UrlProviderMode mode, string culture = null, Uri current = null) + public string GetUrl(int id, UrlMode mode, string culture = null, Uri current = null) => GetUrl(GetDocument(id), mode, culture, current); /// @@ -184,7 +184,7 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it returns "#". /// - public string GetUrl(IPublishedContent content, UrlProviderMode mode, string culture = null, Uri current = null) + public string GetUrl(IPublishedContent content, UrlMode mode, string culture = null, Uri current = null) { if (content == null || content.ItemType == PublishedItemType.Element) return "#"; diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 6c979e9a95..0be392c6dd 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -158,7 +158,7 @@ namespace Umbraco.Web.Templates return match.Value; } - var url = media.Url; + var url = media.Url(); return $"{match.Groups[1].Value}{url}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; }); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b3cab3b2cb..42a1a766e5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1017,7 +1017,6 @@ - diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index d6df5480cd..a39535ba6e 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -208,8 +208,6 @@ namespace Umbraco.Web #region Urls - // fixme do something with these - /// /// Gets the url of a content identified by its identifier. /// @@ -239,7 +237,7 @@ namespace Umbraco.Web /// The mode. /// /// The url for the content. - public string Url(int contentId, UrlProviderMode mode, string culture = null) + public string Url(int contentId, UrlMode mode, string culture = null) { return UrlProvider.GetUrl(contentId, mode, culture); } @@ -251,7 +249,7 @@ namespace Umbraco.Web /// The mode. /// /// The url for the content. - public string Url(Guid contentId, UrlProviderMode mode, string culture = null) + public string Url(Guid contentId, UrlMode mode, string culture = null) { return UrlProvider.GetUrl(contentId, mode, culture); } @@ -262,6 +260,7 @@ namespace Umbraco.Web /// The content identifier. /// /// The absolute url for the content. + [Obsolete("Use the Url() method with UrlMode.Absolute.")] public string UrlAbsolute(int contentId, string culture = null) { return UrlProvider.GetUrl(contentId, true, culture); @@ -273,6 +272,7 @@ namespace Umbraco.Web /// The content identifier. /// /// The absolute url for the content. + [Obsolete("Use the Url() method with UrlMode.Absolute.")] public string UrlAbsolute(Guid contentId, string culture = null) { return UrlProvider.GetUrl(contentId, true, culture); diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs index 6f7fbacf7a..8fe0524209 100644 --- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -385,7 +385,7 @@ namespace Umbraco.Web if (urlHelper.RequestContext.HttpContext.Request.Url != null) { var requestUrl = urlHelper.RequestContext.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority); - return string.Format("{0}{1}", requestUrl, mediaItem.Url); + return string.Format("{0}{1}", requestUrl, mediaItem.Url()); } return null; } From afda7a566145eb45741fe33c4cee97ac126dd7d8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Apr 2019 21:15:13 +0200 Subject: [PATCH 010/218] Refactor IPublishedContent.ItemType --- .../Models/PublishedContent/IPublishedContent.cs | 5 ----- .../Models/PublishedContent/PublishedContentWrapped.cs | 3 --- .../LegacyXmlPublishedCache/DictionaryPublishedContent.cs | 5 ----- .../LegacyXmlPublishedCache/XmlPublishedContent.cs | 2 -- src/Umbraco.Tests/Published/NestedContentTests.cs | 1 - .../PublishedContent/PublishedContentDataTableTests.cs | 2 -- src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs | 1 - src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs | 1 - src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs | 2 -- src/Umbraco.Web/Models/PublishedContentBase.cs | 5 +---- .../ValueConverters/ContentPickerValueConverter.cs | 2 +- .../ValueConverters/MultiNodeTreePickerValueConverter.cs | 2 +- .../ValueConverters/MultiUrlPickerValueConverter.cs | 2 +- src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs | 5 +---- src/Umbraco.Web/PublishedCache/PublishedMember.cs | 2 -- src/Umbraco.Web/Routing/UrlProvider.cs | 2 +- 16 files changed, 6 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 04a33fc0bc..978958413a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -118,11 +118,6 @@ namespace Umbraco.Core.Models.PublishedContent /// IReadOnlyDictionary Cultures { get; } - /// - /// Gets the type of the content item (document, media...). - /// - PublishedItemType ItemType { get; } - /// /// Gets a value indicating whether the content is draft. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index ee7ed31d1d..cd92888f7e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -102,9 +102,6 @@ namespace Umbraco.Core.Models.PublishedContent /// public IReadOnlyDictionary Cultures => _content.Cultures; - /// - public virtual PublishedItemType ItemType => _content.ItemType; - /// public virtual bool IsDraft(string culture = null) => _content.IsDraft(culture); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index 9e615e6745..40d7146248 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -139,11 +139,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly Func _getProperty; private readonly IAppCache _appCache; - /// - /// Returns 'Media' as the item type - /// - public override PublishedItemType ItemType => PublishedItemType.Media; - public override IPublishedContent Parent => _getParent.Value; public int ParentId { get; private set; } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index 8f30bdd789..b738b22e2c 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -91,8 +91,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _properties.TryGetValue(alias, out property) ? property : null; } - public override PublishedItemType ItemType => PublishedItemType.Content; - public override IPublishedContent Parent { get diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 1cf291b263..e91f456922 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -272,7 +272,6 @@ namespace Umbraco.Tests.Published } // ReSharper disable UnassignedGetOnlyAutoProperty - public override PublishedItemType ItemType { get; } public override bool IsDraft(string culture = null) => false; public override bool IsPublished(string culture = null) => true; public override IPublishedContent Parent { get; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 69eca7ef88..41b5588234 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -188,8 +188,6 @@ namespace Umbraco.Tests.PublishedContent public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => default; - public PublishedItemType ItemType { get; set; } - IPublishedContent IPublishedContent.Parent { get { return Parent; } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 91fd1efb92..77f25c0b0b 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -199,7 +199,6 @@ namespace Umbraco.Tests.PublishedContent public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => _urls.TryGetValue(culture ?? "", out var url) ? url : null; public void SetUrl(string url, string culture = null) => _urls[culture ?? ""] = url; - public PublishedItemType ItemType { get { return PublishedItemType.Content; } } public bool IsDraft(string culture = null) => false; public bool IsPublished(string culture = null) => true; diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 4262851fc9..8a092c3453 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -50,7 +50,6 @@ namespace Umbraco.Tests.TestHelpers.Stubs public Guid Version { get; set; } public int Level { get; set; } public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => throw new NotSupportedException(); - public PublishedItemType ItemType => ContentType.ItemType; public bool IsDraft(string culture = null) => false; public bool IsPublished(string culture = null) => true; public IPublishedContent Parent { get; set; } diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 1fafefdcc5..108f58e929 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -279,8 +279,6 @@ namespace Umbraco.Web.Macros public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => throw new NotSupportedException(); - public PublishedItemType ItemType => PublishedItemType.Content; - public bool IsDraft(string culture = null) { throw new NotImplementedException(); diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index e46bb8fddd..7017e84882 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -83,7 +83,7 @@ namespace Umbraco.Web.Models /// public virtual string Url(string culture = null, UrlMode mode = UrlMode.Auto) { - switch (ItemType) + switch (ContentType.ItemType) { case PublishedItemType.Content: var umbracoContext = UmbracoContextAccessor.UmbracoContext; @@ -140,9 +140,6 @@ namespace Umbraco.Web.Models /// public abstract IReadOnlyDictionary Cultures { get; } - /// - public abstract PublishedItemType ItemType { get; } - /// public abstract bool IsDraft(string culture = null); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs index 056334cfa5..8b3655f0cc 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (udi == null) return null; content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udi.Guid); - if (content != null && content.ItemType == PublishedItemType.Content) + if (content != null && content.ContentType.ItemType == PublishedItemType.Content) return content; } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index f80f8f510f..0ee6ff6f6c 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -99,7 +99,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters break; } - if (multiNodeTreePickerItem != null && multiNodeTreePickerItem.ItemType != PublishedItemType.Element) + if (multiNodeTreePickerItem != null && multiNodeTreePickerItem.ContentType.ItemType != PublishedItemType.Element) { multiNodeTreePicker.Add(multiNodeTreePickerItem); } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs index 8e9ee9d6f8..2ceac6cfa8 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(preview, dto.Udi.Guid) : _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(preview, dto.Udi.Guid); - if (content == null || content.ItemType == PublishedItemType.Element) + if (content == null || content.ContentType.ItemType == PublishedItemType.Element) { continue; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index e51220dfab..7f67aff870 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -280,9 +280,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - /// - public override PublishedItemType ItemType => _contentNode.ContentType.ItemType; - /// public override bool IsDraft(string culture = null) { @@ -421,7 +418,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var publishedSnapshot = (PublishedSnapshot)_publishedSnapshotAccessor.PublishedSnapshot; var cache = publishedSnapshot == null ? null - : ((IsPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) && (ItemType != PublishedItemType.Member) + : ((IsPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) && (ContentType.ItemType != PublishedItemType.Member) ? publishedSnapshot.ElementsCache : publishedSnapshot.SnapshotCache); return cache; diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 733b4068e0..68b742caa3 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -78,8 +78,6 @@ namespace Umbraco.Web.PublishedCache #region IPublishedContent - public override PublishedItemType ItemType => PublishedItemType.Member; - public override bool IsDraft(string culture = null) => false; public override bool IsPublished(string culture = null) => true; diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index 3e07c8020a..f40aa00b01 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -186,7 +186,7 @@ namespace Umbraco.Web.Routing /// public string GetUrl(IPublishedContent content, UrlMode mode, string culture = null, Uri current = null) { - if (content == null || content.ItemType == PublishedItemType.Element) + if (content == null || content.ContentType.ItemType == PublishedItemType.Element) return "#"; // this the ONLY place where we deal with default culture - IUrlProvider always receive a culture From 7cf13cbf94b3888e7a19080ed1ab9644e9d87f92 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Apr 2019 11:29:00 +0200 Subject: [PATCH 011/218] Fix IPublishedContent.Name(), .Segment() for invariant --- .../PublishedContent/NuCacheTests.cs | 2 +- .../NuCache/PublishedContent.cs | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 89c2458f18..073ebf58ca 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -249,7 +249,7 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.GetPropertyType("prop").Variations); // now, "no culture" means "invariant" - Assert.IsNull(againContent.Name()); // no invariant name for varying content + Assert.AreEqual("It Works1!", againContent.Name()); Assert.AreEqual("val1", againContent.Value("prop")); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 7f67aff870..2e745aacb0 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -187,31 +187,31 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override string Name(string culture = null) { - // handle context culture + // invariant has invariant value (whatever the requested culture) + if (!ContentType.VariesByCulture()) + return ContentData.Name; + + // handle context culture for variant if (culture == null) culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - // invariant culture - if (culture == "") - return ContentType.VariesByCulture() ? null : ContentData.Name; - - // explicit culture - return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.Name : null; + // get + return culture != "" && Cultures.TryGetValue(culture, out var infos) ? infos.Name : null; } /// public override string UrlSegment(string culture = null) { - // handle context culture + // invariant has invariant value (whatever the requested culture) + if (!ContentType.VariesByCulture()) + return _urlSegment; + + // handle context culture fpr variant if (culture == null) culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - // invariant culture - if (culture == "") - return ContentType.VariesByCulture() ? null : _urlSegment; - - // explicit culture - return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos.UrlSegment : null; + // get + return culture != "" && Cultures.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; } /// From e062ea8d31238fc53abf86259ddfab3874d1595a Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Apr 2019 10:03:49 +0200 Subject: [PATCH 012/218] Refactor IPublishedContent.CultureDate(), .Cultures --- .../PublishedContent/IPublishedContent.cs | 11 +++--- .../PublishedContentWrapped.cs | 4 +- .../DictionaryPublishedContent.cs | 7 ++-- .../XmlPublishedContent.cs | 7 ++-- .../Published/NestedContentTests.cs | 4 +- .../PublishedContent/NuCacheTests.cs | 4 +- .../PublishedContentDataTableTests.cs | 4 +- .../SolidPublishedSnapshot.cs | 4 +- .../TestHelpers/Stubs/TestPublishedContent.cs | 13 ++++--- .../Editors/MacroRenderingController.cs | 18 +++++++-- .../PublishedContentHashtableConverter.cs | 29 +++++++------- .../Models/PublishedContentBase.cs | 4 +- .../NuCache/PublishedContent.cs | 39 ++++++++----------- .../PublishedCache/PublishedMember.cs | 4 +- src/Umbraco.Web/PublishedContentExtensions.cs | 2 +- src/Umbraco.Web/Routing/PublishedRouter.cs | 2 +- .../Routing/RedirectTrackingComponent.cs | 4 +- 17 files changed, 84 insertions(+), 76 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 978958413a..2d1c48b854 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -88,7 +88,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// /// For published content items, this is also the date the item was published. - /// This date is always global to the content item, see GetCulture().Date for the + /// This date is always global to the content item, see CultureDate() for the /// date each culture was published. /// DateTime UpdateDate { get; } @@ -104,19 +104,20 @@ namespace Umbraco.Core.Models.PublishedContent string Url(string culture = null, UrlMode mode = UrlMode.Auto); /// - /// Gets culture infos for a culture. + /// Gets the culture date of the content item. /// - PublishedCultureInfo GetCulture(string culture = null); + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). + DateTime CultureDate(string culture = null); /// - /// Gets culture infos. + /// Gets all available cultures. /// /// /// Contains only those culture that are available. For a published content, these are /// the cultures that are published. For a draft content, those that are 'available' ie /// have a non-empty content name. /// - IReadOnlyDictionary Cultures { get; } + IReadOnlyList Cultures { get; } /// /// Gets a value indicating whether the content is draft. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index cd92888f7e..9132fb1f85 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -97,10 +97,10 @@ namespace Umbraco.Core.Models.PublishedContent public virtual string Url(string culture = null, UrlMode mode = UrlMode.Auto) => _content.Url(culture, mode); /// - public PublishedCultureInfo GetCulture(string culture = null) => _content.GetCulture(culture); + public DateTime CultureDate(string culture = null) => _content.CultureDate(culture); /// - public IReadOnlyDictionary Cultures => _content.Cultures; + public IReadOnlyList Cultures => _content.Cultures; /// public virtual bool IsDraft(string culture = null) => _content.IsDraft(culture); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index 40d7146248..7c6ecf5934 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -153,10 +153,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override string Name(string culture = null) => _name; - public override PublishedCultureInfo GetCulture(string culture = null) => null; + public override DateTime CultureDate(string culture = null) => UpdateDate; - private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); - public override IReadOnlyDictionary Cultures => NoCultures.Value; + // ReSharper disable once CollectionNeverUpdated.Local + private static readonly List EmptyListOfString = new List(); + public override IReadOnlyList Cultures => EmptyListOfString; public override string UrlSegment(string culture = null) => _urlName; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index b738b22e2c..a1395b46bc 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -142,10 +142,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _name; } - public override PublishedCultureInfo GetCulture(string culture = null) => null; + public override DateTime CultureDate(string culture = null) => UpdateDate; - private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); - public override IReadOnlyDictionary Cultures => NoCultures.Value; + // ReSharper disable once CollectionNeverUpdated.Local + private static readonly List EmptyListOfString = new List(); + public override IReadOnlyList Cultures => EmptyListOfString; public override string WriterName { diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index e91f456922..b92724b033 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -284,8 +284,8 @@ namespace Umbraco.Tests.Published public override int? TemplateId { get; } public override int SortOrder { get; } public override string Name(string culture = null) => default; - public override PublishedCultureInfo GetCulture(string culture = ".") => throw new NotSupportedException(); - public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public override DateTime CultureDate(string culture = null) => throw new NotSupportedException(); + public override IReadOnlyList Cultures => throw new NotSupportedException(); public override string UrlSegment(string culture = null) => default; public override string WriterName { get; } public override string CreatorName { get; } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 073ebf58ca..7d5fb8e736 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -223,13 +223,13 @@ namespace Umbraco.Tests.PublishedContent _variationAccesor.VariationContext = new VariationContext("fr-FR"); Assert.AreEqual("val-fr1", publishedContent.Value("prop")); Assert.AreEqual("name-fr1", publishedContent.Name()); - Assert.AreEqual(new DateTime(2018, 01, 01, 01, 00, 00), publishedContent.GetCulture().Date); + Assert.AreEqual(new DateTime(2018, 01, 01, 01, 00, 00), publishedContent.CultureDate()); // now uk is default _variationAccesor.VariationContext = new VariationContext("en-UK"); Assert.AreEqual("val-uk1", publishedContent.Value("prop")); Assert.AreEqual("name-uk1", publishedContent.Name()); - Assert.AreEqual(new DateTime(2018, 01, 02, 01, 00, 00), publishedContent.GetCulture().Date); + Assert.AreEqual(new DateTime(2018, 01, 02, 01, 00, 00), publishedContent.CultureDate()); // invariant needs to be retrieved explicitly, when it's not default Assert.AreEqual("val1", publishedContent.Value("prop", culture: "")); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 41b5588234..7f3c77cc72 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -205,8 +205,8 @@ namespace Umbraco.Tests.PublishedContent public int SortOrder { get; set; } public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; - public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); - public IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public DateTime CultureDate(string culture = null) => throw new NotSupportedException(); + public IReadOnlyList Cultures => throw new NotSupportedException(); public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string WriterName { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 77f25c0b0b..22d6fe8ef1 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -183,8 +183,8 @@ namespace Umbraco.Tests.PublishedContent public int SortOrder { get; set; } public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; - public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); - public IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public DateTime CultureDate(string culture = null) => throw new NotSupportedException(); + public IReadOnlyList Cultures => throw new NotSupportedException(); public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string WriterName { get; set; } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 8a092c3453..6dd25b966e 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -9,12 +9,13 @@ namespace Umbraco.Tests.TestHelpers.Stubs { private readonly Dictionary _names = new Dictionary(); private readonly Dictionary _urlSegments = new Dictionary(); + private readonly Dictionary _cultures; - public TestPublishedContent(IPublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) + public TestPublishedContent(IPublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) : base(contentType, key, values, previewing) { Id = id; - Cultures = cultures; + _cultures = cultures ?? new Dictionary(); } public int Id { get; } @@ -23,19 +24,19 @@ namespace Umbraco.Tests.TestHelpers.Stubs public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public IVariationContextAccessor VariationContextAccessor { get; set; } - public PublishedCultureInfo GetCulture(string culture = null) + public DateTime CultureDate(string culture = null) { // handle context culture if (culture == null) culture = VariationContextAccessor?.VariationContext?.Culture; // no invariant culture infos - if (culture == "" || Cultures == null) return null; + if (culture == "" || Cultures == null) return UpdateDate; // get - return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos : null; + return _cultures.TryGetValue(culture, out var date) ? date : DateTime.MinValue; } - public IReadOnlyDictionary Cultures { get; set; } + public IReadOnlyList Cultures { get; set; } public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string DocumentTypeAlias => ContentType.Alias; diff --git a/src/Umbraco.Web/Editors/MacroRenderingController.cs b/src/Umbraco.Web/Editors/MacroRenderingController.cs index a2bbfe1dfd..64706a7f04 100644 --- a/src/Umbraco.Web/Editors/MacroRenderingController.cs +++ b/src/Umbraco.Web/Editors/MacroRenderingController.cs @@ -123,12 +123,22 @@ namespace Umbraco.Web.Editors // Since a Macro might contain thing thats related to the culture of the "IPublishedContent" (ie Dictionary keys) we want // to set the current culture to the culture related to the content item. This is hacky but it works. - var culture = publishedContent.GetCulture(); - _variationContextAccessor.VariationContext = new VariationContext(); //must have an active variation context! + // fixme I don't even know how this ever worked?! + + // assume this was some sort of "the culture of the item" + // but... with multilingual it does not make any sense?! + //var culture = publishedContent.GetCulture(); + + string culture = ""; // needs to be eg fr-FR + if (culture != null) { - Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture.Culture); - _variationContextAccessor.VariationContext = new VariationContext(Thread.CurrentThread.CurrentCulture.Name); + Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture); + _variationContextAccessor.VariationContext = new VariationContext(culture); + } + else + { + _variationContextAccessor.VariationContext = new VariationContext(); //must have an active variation context! } var result = Request.CreateResponse(); diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 108f58e929..be7bbc37a8 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -230,32 +230,31 @@ namespace Umbraco.Web.Macros public string Name(string culture = null) => _inner.GetCultureName(culture); - public PublishedCultureInfo GetCulture(string culture = null) + public DateTime CultureDate(string culture = null) { - // handle context culture + // invariant has invariant value (whatever the requested culture) + if (!ContentType.VariesByCulture()) + return UpdateDate; + + // handle context culture for variant if (culture == null) culture = _variationContextAccessor.VariationContext.Culture; - // no invariant culture infos - if (culture == "") return null; - // get - return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos : null; + return culture != "" && _inner.PublishCultureInfos.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue; } - public IReadOnlyDictionary Cultures + // ReSharper disable once CollectionNeverUpdated.Local + private static readonly List EmptyListOfString = new List(); + private IReadOnlyList _cultures; + + public IReadOnlyList Cultures { get { if (!_inner.ContentType.VariesByCulture()) - return NoCultureInfos; - - if (_cultureInfos != null) - return _cultureInfos; - - var urlSegmentProviders = Current.UrlSegmentProviders; // TODO inject - return _cultureInfos = _inner.PublishCultureInfos.Values - .ToDictionary(x => x.Culture, x => new PublishedCultureInfo(x.Culture, x.Name, _inner.GetUrlSegment(urlSegmentProviders, x.Culture), x.Date)); + return EmptyListOfString; + return _cultures ?? (_cultures = _inner.PublishCultureInfos.Values.Select(x => x.Culture).ToList()); } } diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 7017e84882..22a76fb907 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -135,10 +135,10 @@ namespace Umbraco.Web.Models } /// - public abstract PublishedCultureInfo GetCulture(string culture = null); + public abstract DateTime CultureDate(string culture = null); /// - public abstract IReadOnlyDictionary Cultures { get; } + public abstract IReadOnlyList Cultures { get; } /// public abstract bool IsDraft(string culture = null); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 2e745aacb0..86ed272b13 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -196,7 +196,7 @@ namespace Umbraco.Web.PublishedCache.NuCache culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; // get - return culture != "" && Cultures.TryGetValue(culture, out var infos) ? infos.Name : null; + return culture != "" && ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.Name : null; } /// @@ -206,12 +206,12 @@ namespace Umbraco.Web.PublishedCache.NuCache if (!ContentType.VariesByCulture()) return _urlSegment; - // handle context culture fpr variant + // handle context culture for variant if (culture == null) culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; // get - return culture != "" && Cultures.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; + return ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; } /// @@ -244,39 +244,34 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override DateTime UpdateDate => ContentData.VersionDate; - private IReadOnlyDictionary _cultureInfos; - - private static readonly IReadOnlyDictionary NoCultureInfos = new Dictionary(); - /// - public override PublishedCultureInfo GetCulture(string culture = null) + public override DateTime CultureDate(string culture = null) { - // handle context culture + // invariant has invariant value (whatever the requested culture) + if (!ContentType.VariesByCulture()) + return UpdateDate; + + // handle context culture for variant if (culture == null) culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - // no invariant culture infos - if (culture == "") return null; - // get - return Cultures.TryGetValue(culture, out var cultureInfos) ? cultureInfos : null; + return culture != "" && ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue; } + // ReSharper disable once CollectionNeverUpdated.Local + private static readonly List EmptyListOfString = new List(); + private IReadOnlyList _cultures; + /// - public override IReadOnlyDictionary Cultures + public override IReadOnlyList Cultures { get { if (!ContentType.VariesByCulture()) - return NoCultureInfos; + return EmptyListOfString; - if (_cultureInfos != null) return _cultureInfos; - - if (ContentData.CultureInfos == null) - throw new Exception("oops: _contentDate.CultureInfos is null."); - - return _cultureInfos = ContentData.CultureInfos - .ToDictionary(x => x.Key, x => new PublishedCultureInfo(x.Key, x.Value.Name, x.Value.UrlSegment, x.Value.Date), StringComparer.OrdinalIgnoreCase); + return _cultures ?? (_cultures = ContentData.CultureInfos.Keys.ToList()); } } diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 68b742caa3..46b12b5ca3 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -140,9 +140,9 @@ namespace Umbraco.Web.PublishedCache return _member.Name; } - public override PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); + public override DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public override IReadOnlyList Cultures => throw new NotSupportedException(); public override string UrlSegment(string culture = null) => throw new NotSupportedException(); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 7776a7c8ac..d7996034a8 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -168,7 +168,7 @@ namespace Umbraco.Web /// /// Culture is case-insensitive. public static bool HasCulture(this IPublishedContent content, string culture) - => content.Cultures.ContainsKey(culture ?? string.Empty); + => content.Cultures.Contains(culture ?? string.Empty); /// /// Filters a sequence of to return invariant items, and items that are published for the specified culture. diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs index c93b7c06b2..aeb329a9e9 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -276,7 +276,7 @@ namespace Umbraco.Web.Routing return true; // variant, ensure that the culture corresponding to the domain's language is published - return domainDocument.Cultures.ContainsKey(domain.Culture.Name); + return domainDocument.Cultures.Contains(domain.Culture.Name); } domains = domains.Where(IsPublishedContentDomain).ToList(); diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs index 0d82467179..4f9086e50b 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs @@ -171,12 +171,12 @@ namespace Umbraco.Web.Routing if (entityContent == null) continue; // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) - var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Select(c => c.Key).ToArray() + var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.ToArray() ?? new[] {(string) null}; foreach (var x in entityContent.DescendantsOrSelf()) { // if this entity defines specific cultures, use those instead of the default ones - var cultures = x.Cultures.Any() ? x.Cultures.Select(c => c.Key) : defaultCultures; + var cultures = x.Cultures.Any() ? x.Cultures : defaultCultures; foreach (var culture in cultures) { From a996d46b6ff85bd927b0a331f37de6d36f123529 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Apr 2019 14:41:54 +0200 Subject: [PATCH 013/218] Refactor IPublishedContent.GetCultureFromDomains() --- .../LegacyXmlPublishedCache/DomainCache.cs | 9 +- .../PublishedContentCache.cs | 8 +- src/Umbraco.Web/Editors/ContentController.cs | 2 +- .../Editors/MacroRenderingController.cs | 20 ++--- .../PublishedCache/IDomainCache.cs | 25 ++++-- .../PublishedCache/NuCache/ContentCache.cs | 12 ++- .../PublishedCache/NuCache/DomainCache.cs | 19 ++++- .../NuCache/PublishedSnapshotService.cs | 5 +- src/Umbraco.Web/PublishedContentExtensions.cs | 25 ++++++ src/Umbraco.Web/Routing/AliasUrlProvider.cs | 7 +- .../Routing/ContentFinderByConfigured404.cs | 2 +- .../Routing/ContentFinderByRedirectUrl.cs | 2 +- src/Umbraco.Web/Routing/ContentFinderByUrl.cs | 2 +- .../Routing/ContentFinderByUrlAndTemplate.cs | 2 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 10 +-- src/Umbraco.Web/Routing/DomainAndUri.cs | 2 +- .../{DomainHelper.cs => DomainUtilities.cs} | 83 +++++++++++++------ src/Umbraco.Web/Routing/PublishedRouter.cs | 4 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- src/Umbraco.Web/UmbracoContext.cs | 20 ----- src/Umbraco.Web/UmbracoHelper.cs | 6 -- 21 files changed, 153 insertions(+), 114 deletions(-) rename src/Umbraco.Web/Routing/{DomainHelper.cs => DomainUtilities.cs} (81%) diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs index cde2077551..abaa239598 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs @@ -27,13 +27,18 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } /// - public IEnumerable GetAssigned(int contentId, bool includeWildcards) + public IEnumerable GetAssigned(int documentId, bool includeWildcards = false) { - return _domainService.GetAssignedDomains(contentId, includeWildcards) + return _domainService.GetAssignedDomains(documentId, includeWildcards) .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)); } + /// + public bool HasAssigned(int documentId, bool includeWildcards = false) + => documentId > 0 && GetAssigned(documentId, includeWildcards).Any(); + + /// public string DefaultCulture { get; } } } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 1ad6e045c6..1ccbbf950b 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -22,7 +22,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly RoutesCache _routesCache; private readonly IDomainCache _domainCache; - private readonly DomainHelper _domainHelper; private readonly PublishedContentTypeCache _contentTypeCache; // initialize a PublishedContentCache instance with @@ -48,7 +47,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _routesCache = routesCache; // may be null for unit-testing _contentTypeCache = contentTypeCache; _domainCache = domainCache; - _domainHelper = new DomainHelper(_domainCache, siteDomainHelper); _xmlStore = xmlStore; _xml = _xmlStore.Xml; // capture - because the cache has to remain consistent @@ -107,7 +105,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // that would be returned - the "deepest" route - and that is the route we want to cache, *not* the // longer one - so make sure we don't cache the wrong route - var deepest = DomainHelper.ExistsDomainInPath(_domainCache.GetAll(false), content.Path, domainRootNodeId) == false; + var deepest = DomainUtilities.ExistsDomainInPath(_domainCache.GetAll(false), content.Path, domainRootNodeId) == false; if (deepest) _routesCache.Store(content.Id, route, true); // trusted route @@ -267,7 +265,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // or we reach the content root, collecting urls in the way var pathParts = new List(); var n = node; - var hasDomains = _domainHelper.NodeHasDomains(n.Id); + var hasDomains = _domainCache.HasAssigned(n.Id); while (hasDomains == false && n != null) // n is null at root { // get the url @@ -276,7 +274,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // move to parent node n = n.Parent; - hasDomains = n != null && _domainHelper.NodeHasDomains(n.Id); + hasDomains = n != null && _domainCache.HasAssigned(n.Id); } // no domain, respect HideTopLevelNodeFromPath for legacy purposes diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 4b23e46a6c..a8824d416f 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1656,7 +1656,7 @@ namespace Umbraco.Web.Editors { try { - var uri = DomainHelper.ParseUriFromDomainName(domain.Name, Request.RequestUri); + var uri = DomainUtilities.ParseUriFromDomainName(domain.Name, Request.RequestUri); } catch (UriFormatException) { diff --git a/src/Umbraco.Web/Editors/MacroRenderingController.cs b/src/Umbraco.Web/Editors/MacroRenderingController.cs index 64706a7f04..0c3b1626d0 100644 --- a/src/Umbraco.Web/Editors/MacroRenderingController.cs +++ b/src/Umbraco.Web/Editors/MacroRenderingController.cs @@ -123,23 +123,17 @@ namespace Umbraco.Web.Editors // Since a Macro might contain thing thats related to the culture of the "IPublishedContent" (ie Dictionary keys) we want // to set the current culture to the culture related to the content item. This is hacky but it works. - // fixme I don't even know how this ever worked?! + // fixme + // in a 1:1 situation we do not handle the language being edited + // so the macro renders in the wrong language - // assume this was some sort of "the culture of the item" - // but... with multilingual it does not make any sense?! - //var culture = publishedContent.GetCulture(); - - string culture = ""; // needs to be eg fr-FR + var culture = publishedContent.GetCultureFromDomains(); if (culture != null) - { Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture); - _variationContextAccessor.VariationContext = new VariationContext(culture); - } - else - { - _variationContextAccessor.VariationContext = new VariationContext(); //must have an active variation context! - } + + // must have an active variation context! + _variationContextAccessor.VariationContext = new VariationContext(culture); var result = Request.CreateResponse(); //need to create a specific content result formatted as HTML since this controller has been configured diff --git a/src/Umbraco.Web/PublishedCache/IDomainCache.cs b/src/Umbraco.Web/PublishedCache/IDomainCache.cs index dbee8908a0..3ec84c9d48 100644 --- a/src/Umbraco.Web/PublishedCache/IDomainCache.cs +++ b/src/Umbraco.Web/PublishedCache/IDomainCache.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Routing; namespace Umbraco.Web.PublishedCache @@ -6,20 +8,29 @@ namespace Umbraco.Web.PublishedCache public interface IDomainCache { /// - /// Returns all in the current domain cache including any domains that may be referenced by content items that are no longer published + /// Gets all in the current domain cache, including any domains that may be referenced by documents that are no longer published. /// /// /// IEnumerable GetAll(bool includeWildcards); /// - /// Returns all assigned for the content id specified even if the content item is not published + /// Gets all assigned for specified document, even if it is not published. /// - /// - /// - /// - IEnumerable GetAssigned(int contentId, bool includeWildcards); + /// The document identifier. + /// A value indicating whether to consider wildcard domains. + IEnumerable GetAssigned(int documentId, bool includeWildcards = false); + /// + /// Determines whether a document has domains. + /// + /// The document identifier. + /// A value indicating whether to consider wildcard domains. + bool HasAssigned(int documentId, bool includeWildcards = false); + + /// + /// Gets the system default culture. + /// string DefaultCulture { get; } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 08664f0a7a..705ce17595 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -23,9 +23,8 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly ContentStore.Snapshot _snapshot; private readonly IAppCache _snapshotCache; private readonly IAppCache _elementsCache; - private readonly DomainHelper _domainHelper; + private readonly IDomainCache _domainCache; private readonly IGlobalSettings _globalSettings; - private readonly ILocalizationService _localizationService; #region Constructor @@ -34,15 +33,14 @@ namespace Umbraco.Web.PublishedCache.NuCache // it's too late for UmbracoContext which has captured previewDefault and stuff into these ctor vars // but, no, UmbracoContext returns snapshot.Content which comes from elements SO a resync should create a new cache - public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, IAppCache elementsCache, DomainHelper domainHelper, IGlobalSettings globalSettings, ILocalizationService localizationService) + public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, IAppCache elementsCache, IDomainCache domainCache, IGlobalSettings globalSettings) : base(previewDefault) { _snapshot = snapshot; _snapshotCache = snapshotCache; _elementsCache = elementsCache; - _domainHelper = domainHelper; + _domainCache = domainCache; _globalSettings = globalSettings; - _localizationService = localizationService; } private bool HideTopLevelNodeFromPath => _globalSettings.HideTopLevelNodeFromPath; @@ -150,7 +148,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var pathParts = new List(); var n = node; var urlSegment = n.UrlSegment(culture); - var hasDomains = _domainHelper.NodeHasDomains(n.Id); + var hasDomains = _domainCache.HasAssigned(n.Id); while (hasDomains == false && n != null) // n is null at root { // no segment indicates this is not published when this is a variant @@ -163,7 +161,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (n != null) urlSegment = n.UrlSegment(culture); - hasDomains = n != null && _domainHelper.NodeHasDomains(n.Id); + hasDomains = n != null && _domainCache.HasAssigned(n.Id); } // at this point this will be the urlSegment of the root, no segment indicates this is not published when this is a variant diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DomainCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/DomainCache.cs index 896a04a0b3..6bc0de7268 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DomainCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DomainCache.cs @@ -4,16 +4,23 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.PublishedCache.NuCache { + /// + /// Implements for NuCache. + /// internal class DomainCache : IDomainCache { private readonly SnapDictionary.Snapshot _snapshot; + /// + /// Initializes a new instance of the class. + /// public DomainCache(SnapDictionary.Snapshot snapshot, string defaultCulture) { _snapshot = snapshot; - DefaultCulture = defaultCulture; // capture - fast + DefaultCulture = defaultCulture; } + /// public IEnumerable GetAll(bool includeWildcards) { var list = _snapshot.GetAll(); @@ -21,17 +28,23 @@ namespace Umbraco.Web.PublishedCache.NuCache return list; } - public IEnumerable GetAssigned(int contentId, bool includeWildcards) + /// + public IEnumerable GetAssigned(int documentId, bool includeWildcards = false) { // probably this could be optimized with an index // but then we'd need a custom DomainStore of some sort var list = _snapshot.GetAll(); - list = list.Where(x => x.ContentId == contentId); + list = list.Where(x => x.ContentId == documentId); if (includeWildcards == false) list = list.Where(x => x.IsWildcard == false); return list; } + /// + public bool HasAssigned(int documentId, bool includeWildcards = false) + => documentId > 0 && GetAssigned(documentId, includeWildcards).Any(); + + /// public string DefaultCulture { get; } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 3b683cdd4e..ce19764fb6 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -4,13 +4,11 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Web; using CSharpTest.Net.Collections; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -1074,11 +1072,10 @@ namespace Umbraco.Web.PublishedCache.NuCache var defaultCulture = _defaultCultureAccessor.DefaultCulture; var domainCache = new DomainCache(domainSnap, defaultCulture); - var domainHelper = new DomainHelper(domainCache, _siteDomainHelper); return new PublishedSnapshot.PublishedSnapshotElements { - ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainHelper, _globalSettings, _serviceContext.LocalizationService), + ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainCache, _globalSettings), MediaCache = new MediaCache(previewDefault, mediaSnap, snapshotCache, elementsCache), MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor, _entitySerializer), DomainCache = domainCache, diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index d7996034a8..a243d6e77a 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Services; using Umbraco.Examine; using Umbraco.Web.Composing; using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; namespace Umbraco.Web { @@ -20,9 +21,12 @@ namespace Umbraco.Web public static class PublishedContentExtensions { // see notes in PublishedElementExtensions + // (yes, this is not pretty, but works for now) // private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback; private static IPublishedSnapshot PublishedSnapshot => Current.PublishedSnapshot; + private static UmbracoContext UmbracoContext => Current.UmbracoContext; + private static ISiteDomainHelper SiteDomainHelper => Current.Factory.GetInstance(); #region IsComposedOf @@ -186,6 +190,27 @@ namespace Umbraco.Web return contents.Where(x => !x.ContentType.VariesByCulture() || x.HasCulture(culture)); } + /// + /// Gets the culture assigned to a document by domains, in the context of a current Uri. + /// + /// The document. + /// An optional current Uri. + /// The culture assigned to the document by domains. + /// + /// In 1:1 multilingual setup, a document contains several cultures (there is not + /// one document per culture), and domains, withing the context of a current Uri, assign + /// a culture to that document. + /// + public static string GetCultureFromDomains(this IPublishedContent content, Uri current = null) + { + var umbracoContext = UmbracoContext; + + if (umbracoContext == null) + throw new InvalidOperationException("A current UmbracoContext is required."); + + return DomainUtilities.GetCultureFromDomains(content.Id, content.Path, current, umbracoContext, SiteDomainHelper); + } + #endregion #region Search diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index bbe3a9db8c..411fabbf35 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; @@ -60,16 +59,14 @@ namespace Umbraco.Web.Routing if (!node.HasProperty(Constants.Conventions.Content.UrlAlias)) yield break; - var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); - // look for domains, walking up the tree var n = node; - var domainUris = domainHelper.DomainsForNode(n.Id, current, false); + var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, false); while (domainUris == null && n != null) // n is null at root { // move to parent node n = n.Parent; - domainUris = n == null ? null : domainHelper.DomainsForNode(n.Id, current, excludeDefault: false); + domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, excludeDefault: false); } // determine whether the alias property varies diff --git a/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs index e3cad25c6f..e5bb23bfce 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web.Routing } if (node != null) { - var d = DomainHelper.FindWildcardDomainInPath(frequest.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), node.Path, null); + var d = DomainUtilities.FindWildcardDomainInPath(frequest.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), node.Path, null); if (d != null) errorCulture = d.Culture; } diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs index eae2b57378..d7b4c9925c 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Routing public bool TryFindContent(PublishedRequest frequest) { var route = frequest.HasDomain - ? frequest.Domain.ContentId + DomainHelper.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) + ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) : frequest.Uri.GetAbsolutePathDecoded(); var redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route); diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByUrl.cs index 94b2b9dbf2..9aa52782d1 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrl.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Routing { string route; if (frequest.HasDomain) - route = frequest.Domain.ContentId + DomainHelper.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); + route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); else route = frequest.Uri.GetAbsolutePathDecoded(); diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAndTemplate.cs index 1e86b40a79..39c80afd56 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAndTemplate.cs @@ -38,7 +38,7 @@ namespace Umbraco.Web.Routing var path = frequest.Uri.GetAbsolutePathDecoded(); if (frequest.HasDomain) - path = DomainHelper.PathRelativeToDomain(frequest.Domain.Uri, path); + path = DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, path); // no template if "/" if (path == "/") diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 98ba743fe5..06f275a685 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -47,15 +47,13 @@ namespace Umbraco.Web.Routing return null; } - var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); - // extract domainUri and path // route is / or / var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); var domainUri = pos == 0 ? null - : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current, culture); + : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, int.Parse(route.Substring(0, pos)), current, culture); // assemble the url from domainUri (maybe null) and path var url = AssembleUrl(domainUri, path, current, mode).ToString(); @@ -84,15 +82,13 @@ namespace Umbraco.Web.Routing if (node == null) yield break; - var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); - // look for domains, walking up the tree var n = node; - var domainUris = domainHelper.DomainsForNode(n.Id, current, false); + var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, false); while (domainUris == null && n != null) // n is null at root { n = n.Parent; // move to parent node - domainUris = n == null ? null : domainHelper.DomainsForNode(n.Id, current, excludeDefault: true); + domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, excludeDefault: true); } // no domains = exit diff --git a/src/Umbraco.Web/Routing/DomainAndUri.cs b/src/Umbraco.Web/Routing/DomainAndUri.cs index 1151055621..46dc085998 100644 --- a/src/Umbraco.Web/Routing/DomainAndUri.cs +++ b/src/Umbraco.Web/Routing/DomainAndUri.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Routing { try { - Uri = DomainHelper.ParseUriFromDomainName(Name, currentUri); + Uri = DomainUtilities.ParseUriFromDomainName(Name, currentUri); } catch (UriFormatException) { diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainUtilities.cs similarity index 81% rename from src/Umbraco.Web/Routing/DomainHelper.cs rename to src/Umbraco.Web/Routing/DomainUtilities.cs index 95d97653a0..fb0c56b28d 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainUtilities.cs @@ -2,29 +2,69 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Web.PublishedCache; // published snapshot +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.Routing { /// /// Provides utilities to handle domains. /// - public class DomainHelper + public static class DomainUtilities { - private readonly IDomainCache _domainCache; - private readonly ISiteDomainHelper _siteDomainHelper; + #region Document Culture - public DomainHelper(IDomainCache domainCache, ISiteDomainHelper siteDomainHelper) + /// + /// Gets the culture assigned to a document by domains, in the context of a current Uri. + /// + /// The document identifier. + /// The document path. + /// An optional current Uri. + /// An Umbraco context. + /// The site domain helper. + /// The culture assigned to the document by domains. + /// + /// In 1:1 multilingual setup, a document contains several cultures (there is not + /// one document per culture), and domains, withing the context of a current Uri, assign + /// a culture to that document. + /// + internal static string GetCultureFromDomains(int contentId, string contentPath, Uri current, UmbracoContext umbracoContext, ISiteDomainHelper siteDomainHelper) { - _domainCache = domainCache; - _siteDomainHelper = siteDomainHelper; + if (umbracoContext == null) + throw new InvalidOperationException("A current UmbracoContext is required."); + + if (current == null) + current = umbracoContext.CleanedUmbracoUrl; + + // get the published route, else the preview route + // if both are null then the content does not exist + var route = umbracoContext.Content.GetRouteById(contentId) ?? + umbracoContext.Content.GetRouteById(true, contentId); + + if (route == null) + return null; + + var pos = route.IndexOf('/'); + var domain = pos == 0 + ? null + : DomainForNode(umbracoContext.Domains, siteDomainHelper, int.Parse(route.Substring(0, pos)), current); + + var rootContentId = domain?.ContentId ?? -1; + var wcDomain = FindWildcardDomainInPath(umbracoContext.Domains.GetAll(true), contentPath, rootContentId); + + if (wcDomain != null) return wcDomain.Culture.Name; + if (domain != null) return domain.Culture.Name; + return umbracoContext.Domains.DefaultCulture; } - #region Domain for Node + #endregion + + #region Domain for Document /// /// Finds the domain for the specified node, if any, that best matches a specified uri. /// + /// A domain cache. + /// The site domain helper. /// The node identifier. /// The uri, or null. /// The culture, or null. @@ -35,14 +75,14 @@ namespace Umbraco.Web.Routing /// If culture is null, uses the default culture for the installation instead. Otherwise, /// will try with the specified culture, else return null. /// - internal DomainAndUri DomainForNode(int nodeId, Uri current, string culture = null) + internal static DomainAndUri DomainForNode(IDomainCache domainCache, ISiteDomainHelper siteDomainHelper, int nodeId, Uri current, string culture = null) { // be safe if (nodeId <= 0) return null; // get the domains on that node - var domains = _domainCache.GetAssigned(nodeId, false).ToArray(); + var domains = domainCache.GetAssigned(nodeId).ToArray(); // none? if (domains.Length == 0) @@ -50,37 +90,28 @@ namespace Umbraco.Web.Routing // else filter // it could be that none apply (due to culture) - return SelectDomain(domains, current, culture, _domainCache.DefaultCulture, - (cdomainAndUris, ccurrent, cculture, cdefaultCulture) => _siteDomainHelper.MapDomain(cdomainAndUris, ccurrent, cculture, cdefaultCulture)); - } - - /// - /// Gets a value indicating whether a specified node has domains. - /// - /// The node identifier. - /// True if the node has domains, else false. - internal bool NodeHasDomains(int nodeId) - { - return nodeId > 0 && _domainCache.GetAssigned(nodeId, false).Any(); + return SelectDomain(domains, current, culture, domainCache.DefaultCulture, siteDomainHelper.MapDomain); } /// /// Find the domains for the specified node, if any, that match a specified uri. /// + /// A domain cache. + /// The site domain helper. /// The node identifier. /// The uri, or null. /// A value indicating whether to exclude the current/default domain. True by default. /// The domains and their uris, that match the specified uri, else null. /// If at least a domain is set on the node then the method returns the domains that /// best match the specified uri, else it returns null. - internal IEnumerable DomainsForNode(int nodeId, Uri current, bool excludeDefault = true) + internal static IEnumerable DomainsForNode(IDomainCache domainCache, ISiteDomainHelper siteDomainHelper, int nodeId, Uri current, bool excludeDefault = true) { // be safe if (nodeId <= 0) return null; // get the domains on that node - var domains = _domainCache.GetAssigned(nodeId, false).ToArray(); + var domains = domainCache.GetAssigned(nodeId).ToArray(); // none? if (domains.Length == 0) @@ -90,7 +121,7 @@ namespace Umbraco.Web.Routing var domainAndUris = SelectDomains(domains, current).ToArray(); // filter - return _siteDomainHelper.MapDomains(domainAndUris, current, excludeDefault, null, _domainCache.DefaultCulture).ToArray(); + return siteDomainHelper.MapDomains(domainAndUris, current, excludeDefault, null, domainCache.DefaultCulture).ToArray(); } #endregion @@ -252,7 +283,7 @@ namespace Umbraco.Web.Routing } /// - /// Parses a domain name into a URI. + /// Parses a domain name into a URI. /// /// The domain name to parse /// The currently requested URI. If the domain name is relative, the authority of URI will be used. diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs index aeb329a9e9..2e772cb175 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -284,7 +284,7 @@ namespace Umbraco.Web.Routing var defaultCulture = domainsCache.DefaultCulture; // try to find a domain matching the current request - var domainAndUri = DomainHelper.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); + var domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); // handle domain - always has a contentId and a culture if (domainAndUri != null) @@ -328,7 +328,7 @@ namespace Umbraco.Web.Routing var nodePath = request.PublishedContent.Path; _logger.Debug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath); var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null; - var domain = DomainHelper.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); + var domain = DomainUtilities.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); // always has a contentId and a culture if (domain != null) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 42a1a766e5..28337e6d2c 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1137,7 +1137,7 @@ - + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index a39535ba6e..04052e8be3 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -3,12 +3,10 @@ using System.Collections.Generic; using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; -using Umbraco.Web.Runtime; using Umbraco.Web.Security; namespace Umbraco.Web @@ -20,7 +18,6 @@ namespace Umbraco.Web { private readonly IGlobalSettings _globalSettings; private readonly Lazy _publishedSnapshot; - private DomainHelper _domainHelper; private string _previewToken; private bool? _previewing; @@ -107,9 +104,6 @@ namespace Umbraco.Web /// public IPublishedSnapshot PublishedSnapshot => _publishedSnapshot.Value; - // for unit tests - internal bool HasPublishedSnapshot => _publishedSnapshot.IsValueCreated; - /// /// Gets the published content cache. /// @@ -162,20 +156,6 @@ namespace Umbraco.Web /// public IVariationContextAccessor VariationContextAccessor { get; } - /// - /// Creates and caches an instance of a DomainHelper - /// - /// - /// We keep creating new instances of DomainHelper, it would be better if we didn't have to do that so instead we can - /// have one attached to the UmbracoContext. This method accepts an external ISiteDomainHelper otherwise the UmbracoContext - /// ctor will have to have another parameter added only for this one method which is annoying and doesn't make a ton of sense - /// since the UmbracoContext itself doesn't use this. - /// - /// TODO: The alternative is to have a IDomainHelperAccessor singleton which is cached per UmbracoContext - /// - internal DomainHelper GetDomainHelper(ISiteDomainHelper siteDomainHelper) - => _domainHelper ?? (_domainHelper = new DomainHelper(PublishedSnapshot.Domains, siteDomainHelper)); - /// /// Gets a value indicating whether the request has debugging enabled /// diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 1c6eb28b92..6e7b05691e 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -809,11 +809,5 @@ namespace Umbraco.Web } #endregion - - - - - - } } From 41e14a62c05c21a8864ade7423939712dc1679da Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 22 Apr 2019 11:59:06 +0200 Subject: [PATCH 014/218] Refactor IPublishedContent.Parent() --- .../PublishedContent/IPublishedContent.cs | 2 +- .../PublishedContentWrapped.cs | 2 +- .../PublishedMediaCacheTests.cs | 6 ++--- .../DictionaryPublishedContent.cs | 2 +- .../PublishedContentCache.cs | 4 ++-- .../XmlPublishedContent.cs | 9 +++----- .../Published/NestedContentTests.cs | 2 +- .../PublishedContentDataTableTests.cs | 7 ++---- .../PublishedContentLanguageVariantTests.cs | 4 ++-- .../PublishedContent/PublishedMediaTests.cs | 6 ++--- .../PublishedContent/PublishedRouterTests.cs | 2 +- .../PublishedContent/RootNodeTests.cs | 2 +- .../SolidPublishedSnapshot.cs | 8 ++++--- .../TestHelpers/Stubs/TestPublishedContent.cs | 6 +++-- .../PublishedContentHashtableConverter.cs | 10 ++++---- .../PublishedValueFallback.cs | 2 +- .../Models/PublishedContentBase.cs | 2 +- .../PublishedCache/NuCache/ContentCache.cs | 4 ++-- .../NuCache/PublishedContent.cs | 23 ++++++++----------- .../PublishedCache/PublishedMember.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 18 +++++++-------- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 2 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 2 +- .../Routing/UrlProviderExtensions.cs | 2 +- 24 files changed, 62 insertions(+), 67 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 2d1c48b854..7f15e367c8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -155,7 +155,7 @@ namespace Umbraco.Core.Models.PublishedContent /// Gets the parent of the content item. /// /// The parent of root content is null. - IPublishedContent Parent { get; } + IPublishedContent Parent(); /// /// Gets the children of the content item. diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 9132fb1f85..d0db4d6e09 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -113,7 +113,7 @@ namespace Umbraco.Core.Models.PublishedContent #region Tree /// - public virtual IPublishedContent Parent => _content.Parent; + public virtual IPublishedContent Parent() => _content.Parent(); /// public virtual IEnumerable Children => _content.Children; diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index 2245f600dc..ce89310f35 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -111,7 +111,7 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(mRoot.Name, publishedMedia.Name()); Assert.AreEqual(mRoot.Path, publishedMedia.Path); Assert.AreEqual(mRoot.SortOrder, publishedMedia.SortOrder); - Assert.IsNull(publishedMedia.Parent); + Assert.IsNull(publishedMedia.Parent()); } [TestCase("id")] @@ -212,7 +212,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result)); DoAssert(doc, 1234, key, null, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2); - Assert.AreEqual(null, doc.Parent); + Assert.AreEqual(null, doc.Parent()); } [Test] @@ -228,7 +228,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true)); DoAssert(doc, 2000, key, null, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); - Assert.AreEqual(null, doc.Parent); + Assert.AreEqual(null, doc.Parent()); Assert.AreEqual(2, doc.Children.Count()); Assert.AreEqual(2001, doc.Children.ElementAt(0).Id); Assert.AreEqual(2002, doc.Children.ElementAt(1).Id); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index 7c6ecf5934..3175d0aca0 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -139,7 +139,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly Func _getProperty; private readonly IAppCache _appCache; - public override IPublishedContent Parent => _getParent.Value; + public override IPublishedContent Parent() => _getParent.Value; public int ParentId { get; private set; } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 1ccbbf950b..a4e08178ed 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -273,14 +273,14 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache pathParts.Add(urlName); // move to parent node - n = n.Parent; + n = n.Parent(); hasDomains = n != null && _domainCache.HasAssigned(n.Id); } // no domain, respect HideTopLevelNodeFromPath for legacy purposes if (hasDomains == false && _globalSettings.HideTopLevelNodeFromPath) { - if (node.Parent == null) + if (node.Parent() == null) { var rootNode = GetByRoute(preview, "/", true); if (rootNode == null) diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index a1395b46bc..8981b6c691 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -91,13 +91,10 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _properties.TryGetValue(alias, out property) ? property : null; } - public override IPublishedContent Parent + public override IPublishedContent Parent() { - get - { - EnsureNodeInitialized(andParent: true); - return _parent; - } + EnsureNodeInitialized(andParent: true); + return _parent; } public override int Id diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index b92724b033..31fac026f9 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -274,7 +274,7 @@ namespace Umbraco.Tests.Published // ReSharper disable UnassignedGetOnlyAutoProperty public override bool IsDraft(string culture = null) => false; public override bool IsPublished(string culture = null) => true; - public override IPublishedContent Parent { get; } + public override IPublishedContent Parent() => null; public override IEnumerable Children { get; } public override IPublishedContentType ContentType { get; } // ReSharper restore UnassignedGetOnlyAutoProperty diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 7f3c77cc72..7dddd76b8e 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -188,10 +188,7 @@ namespace Umbraco.Tests.PublishedContent public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => default; - IPublishedContent IPublishedContent.Parent - { - get { return Parent; } - } + IPublishedContent IPublishedContent.Parent() => Parent; IEnumerable IPublishedContent.Children { @@ -238,7 +235,7 @@ namespace Umbraco.Tests.PublishedContent IPublishedContent content = this; while (content != null && (property == null || property.HasValue() == false)) { - content = content.Parent; + content = content.Parent(); property = content == null ? null : content.GetProperty(alias); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index f978c8501b..0b549d51ef 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -178,10 +178,10 @@ namespace Umbraco.Tests.PublishedContent item3.SetUrl("/content-1/content-2/content-3"); item1.Children = new List { item2 }; - item2.Parent = item1; + item2.SetParent(item1); item2.Children = new List { item3 }; - item3.Parent = item2; + item3.SetParent(item2); cache.Add(item1); cache.Add(item2); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index b789eb0ef8..c2cf22f85f 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -416,13 +416,13 @@ namespace Umbraco.Tests.PublishedContent var mSubChild3 = MakeNewMedia("SubChild3", mType, user, mChild1.Id); var publishedRoot = GetNode(mRoot.Id); - Assert.AreEqual(null, publishedRoot.Parent); + Assert.AreEqual(null, publishedRoot.Parent()); var publishedChild1 = GetNode(mChild1.Id); - Assert.AreEqual(mRoot.Id, publishedChild1.Parent.Id); + Assert.AreEqual(mRoot.Id, publishedChild1.Parent().Id); var publishedSubChild1 = GetNode(mSubChild1.Id); - Assert.AreEqual(mChild1.Id, publishedSubChild1.Parent.Id); + Assert.AreEqual(mChild1.Id, publishedSubChild1.Parent().Id); } [Test] diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index 19944a2cd4..ea3a00371a 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -64,7 +64,7 @@ namespace Umbraco.Tests.PublishedContent pc.Setup(content => content.CreateDate).Returns(DateTime.Now); pc.Setup(content => content.UpdateDate).Returns(DateTime.Now); pc.Setup(content => content.Path).Returns("-1,1"); - pc.Setup(content => content.Parent).Returns(() => null); + pc.Setup(content => content.Parent()).Returns(() => null); pc.Setup(content => content.Properties).Returns(new Collection()); pc.Setup(content => content.ContentType).Returns(new PublishedContentType(22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); return pc; diff --git a/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs b/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs index 77eab1dbb7..0af30b4642 100644 --- a/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs +++ b/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.PublishedContent content = ctx.ContentCache.GetById(1046); Assert.IsNotNull(content); Assert.AreEqual(1, content.Level); - Assert.IsNull(content.Parent); + Assert.IsNull(content.Parent()); // non-existing content is null content = ctx.ContentCache.GetById(666); diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 22d6fe8ef1..de9f82c6f7 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -99,7 +99,7 @@ namespace Umbraco.Tests.PublishedContent public override IEnumerable GetAtRoot(bool preview) { - return _content.Values.Where(x => x.Parent == null); + return _content.Values.Where(x => x.Parent() == null); } public override IPublishedContent GetSingleByXPath(bool preview, string xpath, Core.Xml.XPathVariable[] vars) @@ -209,7 +209,9 @@ namespace Umbraco.Tests.PublishedContent public int ParentId { get; set; } public IEnumerable ChildIds { get; set; } - public IPublishedContent Parent { get; set; } + private IPublishedContent _parent; + public IPublishedContent Parent() => _parent; + public void SetParent(IPublishedContent parent) => _parent = parent; public IEnumerable Children { get; set; } #endregion @@ -237,7 +239,7 @@ namespace Umbraco.Tests.PublishedContent IPublishedContent content = this; while (content != null && (property == null || property.HasValue() == false)) { - content = content.Parent; + content = content.Parent(); property = content == null ? null : content.GetProperty(alias); } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 6dd25b966e..4334e24f6d 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -53,7 +53,9 @@ namespace Umbraco.Tests.TestHelpers.Stubs public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => throw new NotSupportedException(); public bool IsDraft(string culture = null) => false; public bool IsPublished(string culture = null) => true; - public IPublishedContent Parent { get; set; } + private IPublishedContent _parent; + public IPublishedContent Parent() => _parent; + public void SetParent(IPublishedContent parent) => _parent = parent; public IEnumerable Children { get; set; } // copied from PublishedContentBase @@ -66,7 +68,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs var firstNonNullProperty = property; while (content != null && (property == null || property.HasValue() == false)) { - content = content.Parent; + content = content.Parent(); property = content?.GetProperty(alias); if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; } diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index be7bbc37a8..209808e282 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -37,7 +37,7 @@ namespace Umbraco.Web.Macros PopulatePageData(frequest.PublishedContent.Id, frequest.PublishedContent.Name(), frequest.PublishedContent.ContentType.Id, frequest.PublishedContent.ContentType.Alias, frequest.PublishedContent.WriterName, frequest.PublishedContent.CreatorName, frequest.PublishedContent.CreateDate, frequest.PublishedContent.UpdateDate, - frequest.PublishedContent.Path, frequest.PublishedContent.Parent?.Id ?? -1); + frequest.PublishedContent.Path, frequest.PublishedContent.Parent()?.Id ?? -1); if (frequest.HasTemplate) { @@ -59,7 +59,7 @@ namespace Umbraco.Web.Macros PopulatePageData(doc.Id, doc.Name(), doc.ContentType.Id, doc.ContentType.Alias, doc.WriterName, doc.CreatorName, doc.CreateDate, doc.UpdateDate, - doc.Path, doc.Parent?.Id ?? -1); + doc.Path, doc.Parent()?.Id ?? -1); if (doc.TemplateId.HasValue) { @@ -182,8 +182,8 @@ namespace Umbraco.Web.Macros { private readonly IContent _inner; private readonly IPublishedProperty[] _properties; - private IReadOnlyDictionary _cultureInfos; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IPublishedContent _parent; private static readonly IReadOnlyDictionary NoCultureInfos = new Dictionary(); @@ -215,7 +215,7 @@ namespace Umbraco.Web.Macros .Cast() .ToArray(); - Parent = new PagePublishedContent(_inner.ParentId); + _parent = new PagePublishedContent(_inner.ParentId); } public IPublishedContentType ContentType { get; } @@ -288,7 +288,7 @@ namespace Umbraco.Web.Macros throw new NotImplementedException(); } - public IPublishedContent Parent { get; } + public IPublishedContent Parent() => _parent; public IEnumerable Children => throw new NotImplementedException(); diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 7b467b6d15..6e0246f416 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -160,7 +160,7 @@ namespace Umbraco.Web.Models.PublishedContent IPublishedProperty property; // if we are here, content's property has no value do { - content = content.Parent; + content = content.Parent(); var propertyType = content?.ContentType.GetPropertyType(alias); diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 22a76fb907..d2b3a3749b 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -151,7 +151,7 @@ namespace Umbraco.Web.Models #region Tree /// - public abstract IPublishedContent Parent { get; } + public abstract IPublishedContent Parent(); /// public abstract IEnumerable Children { get; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 705ce17595..c364f5788a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -157,7 +157,7 @@ namespace Umbraco.Web.PublishedCache.NuCache pathParts.Add(urlSegment); // move to parent node - n = n.Parent; + n = n.Parent(); if (n != null) urlSegment = n.UrlSegment(culture); @@ -206,7 +206,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // "/foo" fails (looking for "/*/foo") we try also "/foo". // this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but // that's the way it works pre-4.10 and we try to be backward compat for the time being - if (content.Parent == null) + if (content.Parent() == null) { var rootNode = GetByRoute(preview, "/", true); if (rootNode == null) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 86ed272b13..a854a8a2ad 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -325,21 +325,18 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Tree /// - public override IPublishedContent Parent + public override IPublishedContent Parent() { - get + // have to use the "current" cache because a PublishedContent can be shared + // amongst many snapshots and other content depend on the snapshots + switch (_contentNode.ContentType.ItemType) { - // have to use the "current" cache because a PublishedContent can be shared - // amongst many snapshots and other content depend on the snapshots - switch (_contentNode.ContentType.ItemType) - { - case PublishedItemType.Content: - return GetContentById(IsPreviewing, _contentNode.ParentContentId); - case PublishedItemType.Media: - return GetMediaById(IsPreviewing, _contentNode.ParentContentId); - default: - throw new Exception($"Panic: unsupported item type \"{_contentNode.ContentType.ItemType}\"."); - } + case PublishedItemType.Content: + return GetContentById(IsPreviewing, _contentNode.ParentContentId); + case PublishedItemType.Media: + return GetMediaById(IsPreviewing, _contentNode.ParentContentId); + default: + throw new Exception($"Panic: unsupported item type \"{_contentNode.ContentType.ItemType}\"."); } } diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 46b12b5ca3..09d01229f3 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -82,7 +82,7 @@ namespace Umbraco.Web.PublishedCache public override bool IsPublished(string culture = null) => true; - public override IPublishedContent Parent => null; + public override IPublishedContent Parent() => null; public override IEnumerable Children => Enumerable.Empty(); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index a243d6e77a..708c9a8e09 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -548,7 +548,7 @@ namespace Umbraco.Web /// This method is here for consistency purposes but does not make much sense. public static IPublishedContent Ancestor(this IPublishedContent content) { - return content.Parent; + return content.Parent(); } /// @@ -680,7 +680,7 @@ namespace Umbraco.Web { if (content == null) throw new ArgumentNullException(nameof(content)); if (orSelf) yield return content; - while ((content = content.Parent) != null) + while ((content = content.Parent()) != null) yield return content; } @@ -884,7 +884,7 @@ namespace Umbraco.Web where T : class, IPublishedContent { if (content == null) throw new ArgumentNullException(nameof(content)); - return content.Parent as T; + return content.Parent() as T; } #endregion @@ -1121,8 +1121,8 @@ namespace Umbraco.Web /// The siblings of the content including the node itself. public static IEnumerable SiblingsAndSelf(this IPublishedContent content, string culture = null) { - return content.Parent != null - ? content.Parent.Children(culture) + return content.Parent() != null + ? content.Parent().Children(culture) : PublishedSnapshot.Content.GetAtRoot().WhereIsInvariantOrHasCulture(culture); } @@ -1135,8 +1135,8 @@ namespace Umbraco.Web /// The siblings of the content including the node itself, of the given content type. public static IEnumerable SiblingsAndSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) { - return content.Parent != null - ? content.Parent.ChildrenOfType(contentTypeAlias, culture) + return content.Parent() != null + ? content.Parent().ChildrenOfType(contentTypeAlias, culture) : PublishedSnapshot.Content.GetAtRoot().OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(culture); } @@ -1150,8 +1150,8 @@ namespace Umbraco.Web public static IEnumerable SiblingsAndSelf(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { - return content.Parent != null - ? content.Parent.Children(culture) + return content.Parent() != null + ? content.Parent().Children(culture) : PublishedSnapshot.Content.GetAtRoot().OfType().WhereIsInvariantOrHasCulture(culture); } diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 411fabbf35..f1c755191e 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.Routing while (domainUris == null && n != null) // n is null at root { // move to parent node - n = n.Parent; + n = n.Parent(); domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, excludeDefault: false); } diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 06f275a685..842f855a0a 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -87,7 +87,7 @@ namespace Umbraco.Web.Routing var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, false); while (domainUris == null && n != null) // n is null at root { - n = n.Parent; // move to parent node + n = n.Parent(); // move to parent node domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, excludeDefault: true); } diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 0a4f033bd2..4b66d56830 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -188,7 +188,7 @@ namespace Umbraco.Web.Routing while (o != null) { l.Add(o.Name()); - o = o.Parent; + o = o.Parent(); } l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; From 2270188668a729da770f4e8a5eedb44747b5f1c5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 22 Apr 2019 15:39:24 +0200 Subject: [PATCH 015/218] Refactor IPublishedContent.Children() --- .../PublishedContent/IPublishedContent.cs | 9 +++++-- .../PublishedContentWrapped.cs | 2 +- .../PublishedMediaCacheTests.cs | 12 +++++----- .../DictionaryPublishedContent.cs | 2 +- .../XmlPublishedContent.cs | 9 +++---- .../Published/NestedContentTests.cs | 2 +- .../PublishedContentDataTableTests.cs | 7 ++---- .../PublishedContentLanguageVariantTests.cs | 24 +++++++++---------- .../PublishedContent/PublishedContentTests.cs | 20 ++++++++-------- .../SolidPublishedSnapshot.cs | 4 +++- .../TestHelpers/Stubs/TestPublishedContent.cs | 4 +++- .../ListChildPagesFromChangeableSource.cshtml | 2 +- .../ListChildPagesFromCurrentPage.cshtml | 2 +- .../ListChildPagesOrderedByDate.cshtml | 2 +- .../ListChildPagesOrderedByName.cshtml | 2 +- .../ListChildPagesOrderedByProperty.cshtml | 2 +- .../ListDescendantsFromCurrentPage.cshtml | 6 ++--- .../ListImagesFromMediaFolder.cshtml | 2 +- .../Templates/Navigation.cshtml | 2 +- .../Templates/SiteMap.cshtml | 2 +- .../PublishedContentHashtableConverter.cs | 2 +- .../Models/PublishedContentBase.cs | 2 +- .../PublishedCache/NuCache/ContentCache.cs | 4 ++-- .../NuCache/PublishedContent.cs | 19 ++++++++------- .../PublishedCache/PublishedMember.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 5 ++-- 26 files changed, 78 insertions(+), 73 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 7f15e367c8..a6482b48ab 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -160,8 +160,13 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the children of the content item. /// - /// Children are sorted by their sortOrder. - IEnumerable Children { get; } + /// The specific culture to get the url children for. If null is used the current culture is used (Default is null). + /// + /// Gets children that are available for the specified culture. + /// Children are sorted by their sortOrder. + /// + // FIXME: can culture be '*'? + IEnumerable Children(string culture = null); #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index d0db4d6e09..5bac22ad24 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -116,7 +116,7 @@ namespace Umbraco.Core.Models.PublishedContent public virtual IPublishedContent Parent() => _content.Parent(); /// - public virtual IEnumerable Children => _content.Children; + public virtual IEnumerable Children(string culture = null) => _content.Children(culture); #endregion diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index ce89310f35..bcd34ef2e3 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -172,9 +172,9 @@ namespace Umbraco.Tests.Cache.PublishedCache child1, child2 }); - Assert.AreEqual(2, dicDoc.Children.Count()); - Assert.AreEqual(222333, dicDoc.Children.ElementAt(0).Id); - Assert.AreEqual(444555, dicDoc.Children.ElementAt(1).Id); + Assert.AreEqual(2, dicDoc.Children().Count()); + Assert.AreEqual(222333, dicDoc.Children().ElementAt(0).Id); + Assert.AreEqual(444555, dicDoc.Children().ElementAt(1).Id); } [Test] @@ -229,9 +229,9 @@ namespace Umbraco.Tests.Cache.PublishedCache DoAssert(doc, 2000, key, null, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); Assert.AreEqual(null, doc.Parent()); - Assert.AreEqual(2, doc.Children.Count()); - Assert.AreEqual(2001, doc.Children.ElementAt(0).Id); - Assert.AreEqual(2002, doc.Children.ElementAt(1).Id); + Assert.AreEqual(2, doc.Children().Count()); + Assert.AreEqual(2001, doc.Children().ElementAt(0).Id); + Assert.AreEqual(2002, doc.Children().ElementAt(1).Id); } private XmlDocument GetMediaXml() diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index 3175d0aca0..cd362cadc0 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -183,7 +183,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override IEnumerable Properties => _properties; - public override IEnumerable Children => _getChildren.Value; + public override IEnumerable Children(string culture = null) => _getChildren.Value; public override IPublishedProperty GetProperty(string alias) { diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index 8981b6c691..bc9ab8010d 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -75,13 +75,10 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private bool _isDraft; - public override IEnumerable Children + public override IEnumerable Children(string culture = null) { - get - { - EnsureNodeInitialized(andChildren: true); - return _children; - } + EnsureNodeInitialized(andChildren: true); + return _children; } public override IPublishedProperty GetProperty(string alias) diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 31fac026f9..8b954d6ab0 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -275,7 +275,7 @@ namespace Umbraco.Tests.Published public override bool IsDraft(string culture = null) => false; public override bool IsPublished(string culture = null) => true; public override IPublishedContent Parent() => null; - public override IEnumerable Children { get; } + public override IEnumerable Children(string culture = null) => Enumerable.Empty(); public override IPublishedContentType ContentType { get; } // ReSharper restore UnassignedGetOnlyAutoProperty diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 7dddd76b8e..077dfd3c94 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -97,7 +97,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetContent(true, 1); //change a doc type alias - var c = (TestPublishedContent)doc.Children.ElementAt(0); + var c = (TestPublishedContent)doc.Children().ElementAt(0); c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -190,10 +190,7 @@ namespace Umbraco.Tests.PublishedContent IPublishedContent IPublishedContent.Parent() => Parent; - IEnumerable IPublishedContent.Children - { - get { return Children; } - } + IEnumerable IPublishedContent.Children(string culture = null) => Children; public IPublishedContent Parent { get; set; } public int Id { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 0b549d51ef..4fe028b1db 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -177,10 +177,10 @@ namespace Umbraco.Tests.PublishedContent item3.SetUrlSegment("content-3"); item3.SetUrl("/content-1/content-2/content-3"); - item1.Children = new List { item2 }; + item1.SetChildren(new List { item2 }); item2.SetParent(item1); - item2.Children = new List { item3 }; + item2.SetChildren(new List { item3 }); item3.SetParent(item2); cache.Add(item1); @@ -247,7 +247,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); var value = content.Value("welcomeText2"); Assert.IsNull(value); } @@ -255,7 +255,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); } @@ -263,7 +263,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested2() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First().Children().First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2"); Assert.IsNull(value); @@ -272,7 +272,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively2() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First().Children().First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); @@ -281,7 +281,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively3() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First().Children().First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First().Children().First(); Assert.IsNull(content.GetProperty("noprop")); var value = content.Value("noprop", fallback: Fallback.ToAncestors); // property has no value but we still get the value (ie, the converter would do something) @@ -292,7 +292,7 @@ namespace Umbraco.Tests.PublishedContent public void Can_Get_Content_With_Recursive_Priority() { Current.VariationContextAccessor.VariationContext = new VariationContext("nl"); - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language)); @@ -303,7 +303,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_With_Fallback_Language_Priority() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.ToLanguage); // No Dutch value is directly assigned. Check has fallen back to English value from language variant. @@ -313,14 +313,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Throws_For_Non_Supported_Fallback() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); Assert.Throws(() => content.Value("welcomeText", "nl", fallback: Fallback.To(999))); } [Test] public void Can_Fallback_To_Default_Value() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); // no Dutch value is assigned, so getting null var value = content.Value("welcomeText", "nl"); @@ -338,7 +338,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Have_Custom_Default_Value() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children.First(); + var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); // HACK: the value, pretend the converter would return something var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 8be13345fb..4472842251 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -169,7 +169,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var items = doc.Children.Where(x => x.IsVisible()).ToIndexedArray(); + var items = doc.Children().Where(x => x.IsVisible()).ToIndexedArray(); foreach (var item in items) { @@ -190,7 +190,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1173); var items = doc - .Children + .Children() .Where(x => x.IsVisible()) .ToIndexedArray(); @@ -245,7 +245,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1173); var ct = doc.ContentType; - var items = doc.Children + var items = doc.Children() .Select(x => x.CreateModel()) // linq, returns IEnumerable // only way around this is to make sure every IEnumerable extension @@ -277,7 +277,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var items = doc.Children.Take(4).ToIndexedArray(); + var items = doc.Children().Take(4).ToIndexedArray(); foreach (var item in items) { @@ -297,7 +297,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - foreach (var d in doc.Children.Skip(1).ToIndexedArray()) + foreach (var d in doc.Children().Skip(1).ToIndexedArray()) { if (d.Content.Id != 1176) { @@ -315,7 +315,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var items = doc.Children + var items = doc.Children() .Concat(new[] { GetNode(1175), GetNode(4444) }) .ToIndexedArray(); @@ -400,7 +400,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1046); - var found1 = doc.Children.GroupBy(x => x.ContentType.Alias).ToArray(); + var found1 = doc.Children().GroupBy(x => x.ContentType.Alias).ToArray(); Assert.AreEqual(2, found1.Length); Assert.AreEqual(2, found1.Single(x => x.Key.ToString() == "Home").Count()); @@ -421,8 +421,8 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1046); - var found1 = doc.Children.Where(x => x.ContentType.Alias == "CustomDocument"); - var found2 = doc.Children.Where(x => x.ContentType.Alias == "Home"); + var found1 = doc.Children().Where(x => x.ContentType.Alias == "CustomDocument"); + var found2 = doc.Children().Where(x => x.ContentType.Alias == "Home"); Assert.AreEqual(1, found1.Count()); Assert.AreEqual(2, found2.Count()); @@ -433,7 +433,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - var ordered = doc.Children.OrderBy(x => x.UpdateDate); + var ordered = doc.Children().OrderBy(x => x.UpdateDate); var correctOrder = new[] { 1178, 1177, 1174, 1176 }; for (var i = 0; i < correctOrder.Length; i++) diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index de9f82c6f7..d61dead9c2 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -212,7 +212,9 @@ namespace Umbraco.Tests.PublishedContent private IPublishedContent _parent; public IPublishedContent Parent() => _parent; public void SetParent(IPublishedContent parent) => _parent = parent; - public IEnumerable Children { get; set; } + private IEnumerable _children; + public IEnumerable Children(string culture = null) => _children; + public void SetChildren(IEnumerable children) => _children = children; #endregion diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 4334e24f6d..8c631bb067 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -56,7 +56,9 @@ namespace Umbraco.Tests.TestHelpers.Stubs private IPublishedContent _parent; public IPublishedContent Parent() => _parent; public void SetParent(IPublishedContent parent) => _parent = parent; - public IEnumerable Children { get; set; } + private IEnumerable _children; + public IEnumerable Children(string culture = null) => _children; + public void SetChildren(IEnumerable children) => _children = children; // copied from PublishedContentBase public IPublishedProperty GetProperty(string alias, bool recurse) diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml index 46c8de695c..495141f821 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml @@ -19,7 +19,7 @@ { @* Get the starting page *@ var startNode = Umbraco.Content(startNodeId); - var selection = startNode.Children.Where(x => x.IsVisible()).ToArray(); + var selection = startNode.Children().Where(x => x.IsVisible()).ToArray(); if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml index e6606d6204..6ba7471899 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml @@ -9,7 +9,7 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).ToArray(); } +@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).ToArray(); } @if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml index 2c2cc4422b..4ace3da7d1 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml @@ -10,7 +10,7 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderByDescending(x => x.CreateDate).ToArray(); } +@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).OrderByDescending(x => x.CreateDate).ToArray(); } @if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml index d0398e7272..ecfd8ebe9d 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml @@ -10,7 +10,7 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderBy(x => x.Name).ToArray(); } +@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).OrderBy(x => x.Name).ToArray(); } @if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml index 1bffae04c4..667a384dfe 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml @@ -17,7 +17,7 @@ @if (propertyAlias != null) { - var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderBy(x => x.Value(propertyAlias.ToString())).ToArray(); + var selection = Model.Content.Children().Where(x => x.IsVisible()).OrderBy(x => x.Value(propertyAlias.ToString())).ToArray(); if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml index 7ae917b41d..196db52d92 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml @@ -7,7 +7,7 @@ the page currently being viewed by the website visitor, displayed as nested unordered HTML lists. *@ -@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).ToArray(); } +@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).ToArray(); } @* Ensure that the Current Page has children *@ @if (selection.Length > 0) @@ -25,7 +25,7 @@ @* if this child page has any children, where the property umbracoNaviHide is not True *@ @{ - var children = item.Children.Where(x => x.IsVisible()).ToArray(); + var children = item.Children().Where(x => x.IsVisible()).ToArray(); if (children.Length > 0) { @* Call our helper to display the children *@ @@ -54,7 +54,7 @@ @* if the page has any children, where the property umbracoNaviHide is not True *@ @{ - var children = item.Children.Where(x => x.IsVisible()).ToArray(); + var children = item.Children().Where(x => x.IsVisible()).ToArray(); if (children.Length > 0) { @* Recurse and call our helper to display the children *@ diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml index 51fdbadb00..b6067ff93a 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml @@ -18,7 +18,7 @@ { @* Get the media item associated with the id passed in *@ var media = Umbraco.Media(mediaId); - var selection = media.Children.ToArray(); + var selection = media.Children().ToArray(); if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml index 1c01eeb855..733c6be801 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml @@ -7,7 +7,7 @@ It also highlights the current active page/section in the navigation with the CSS class "current". *@ -@{ var selection = Model.Content.Root().Children.Where(x => x.IsVisible()).ToArray(); } +@{ var selection = Model.Content.Root().Children().Where(x => x.IsVisible()).ToArray(); } @if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml index 567ed5d07d..a14d6fdc09 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml @@ -23,7 +23,7 @@ const int maxLevelForSitemap = 4; @* Select visible children *@ - var selection = node.Children.Where(x => x.IsVisible() && x.Level <= maxLevelForSitemap).ToArray(); + var selection = node.Children().Where(x => x.IsVisible() && x.Level <= maxLevelForSitemap).ToArray(); @* If any items are returned, render a list *@ if (selection.Length > 0) diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 209808e282..ebeafd2c06 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -290,7 +290,7 @@ namespace Umbraco.Web.Macros public IPublishedContent Parent() => _parent; - public IEnumerable Children => throw new NotImplementedException(); + public IEnumerable Children(string culture = null) => throw new NotImplementedException(); public IEnumerable Properties => _properties; diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index d2b3a3749b..f29dc48d2b 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -154,7 +154,7 @@ namespace Umbraco.Web.Models public abstract IPublishedContent Parent(); /// - public abstract IEnumerable Children { get; } + public abstract IEnumerable Children(string culture = null); #endregion diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index c364f5788a..e422c04f72 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -107,7 +107,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // hideTopLevelNode = support legacy stuff, look for /*/path/to/node // else normal, look for /path/to/node content = hideTopLevelNode.Value - ? GetAtRoot(preview).SelectMany(x => x.Children).FirstOrDefault(x => x.UrlSegment(culture) == parts[0]) + ? GetAtRoot(preview).SelectMany(x => x.Children(culture)).FirstOrDefault(x => x.UrlSegment(culture) == parts[0]) : GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(culture) == parts[0]); content = FollowRoute(content, parts, 1, culture); } @@ -187,7 +187,7 @@ namespace Umbraco.Web.PublishedCache.NuCache while (content != null && i < parts.Count) { var part = parts[i++]; - content = content.Children.FirstOrDefault(x => + content = content.Children(culture).FirstOrDefault(x => { var urlSegment = x.UrlSegment(culture); return urlSegment == part; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index a854a8a2ad..c1215c881b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -341,17 +341,18 @@ namespace Umbraco.Web.PublishedCache.NuCache } /// - public override IEnumerable Children + public override IEnumerable Children(string culture = null) { - get - { - var cache = GetAppropriateCache(); - if (cache == null || PublishedSnapshotService.CachePublishedContentChildren == false) - return GetChildren(); + // FIXME THIS CANNOT WORK + // we cannot cache children this way, they should be a linked list! + throw new NotImplementedException(); - // note: ToArray is important here, we want to cache the result, not the function! - return (IEnumerable)cache.Get(ChildrenCacheKey, () => GetChildren().ToArray()); - } + var cache = GetAppropriateCache(); + if (cache == null || PublishedSnapshotService.CachePublishedContentChildren == false) + return GetChildren(); + + // note: ToArray is important here, we want to cache the result, not the function! + return (IEnumerable)cache.Get(ChildrenCacheKey, () => GetChildren().ToArray()); } private string _childrenCacheKey; diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 09d01229f3..f0c3ac4f5b 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -84,7 +84,7 @@ namespace Umbraco.Web.PublishedCache public override IPublishedContent Parent() => null; - public override IEnumerable Children => Enumerable.Empty(); + public override IEnumerable Children(string culture = null) => Enumerable.Empty(); public override IEnumerable Properties => _properties; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 708c9a8e09..15d4432a90 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -891,6 +891,7 @@ namespace Umbraco.Web #region Axes: children + // FIXME: kill that one /// /// Gets the children of the content. /// @@ -905,7 +906,7 @@ namespace Umbraco.Web { if (content == null) throw new ArgumentNullException(nameof(content)); - return content.Children.WhereIsInvariantOrHasCulture(culture); + return content.Children(culture); //.WhereIsInvariantOrHasCulture(culture); } /// @@ -1029,7 +1030,7 @@ namespace Umbraco.Web //create all row data var tableData = Core.DataTableExtensions.CreateTableData(); //loop through each child and create row data for it - foreach (var n in content.Children.OrderBy(x => x.SortOrder)) + foreach (var n in content.Children().OrderBy(x => x.SortOrder)) { if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false) { From 05c85bbce56292d42f94919870a8ffe7a32ef2f6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 22 Apr 2019 17:51:07 +0200 Subject: [PATCH 016/218] NuCache: children as linked lists --- .../PublishedCache/NuCache/ContentNode.cs | 80 ++---- .../PublishedCache/NuCache/ContentStore.cs | 237 +++++++++++++----- .../NuCache/PublishedContent.cs | 89 ++----- .../PublishedCache/NuCache/Snap/LinkedNode.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 6 +- 5 files changed, 227 insertions(+), 187 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index f7eb2ca19e..58e60cd8ad 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache.NuCache.DataSource; @@ -9,6 +8,10 @@ namespace Umbraco.Web.PublishedCache.NuCache // internal, never exposed, to be accessed from ContentStore (only!) internal class ContentNode { + // special ctor for root pseudo node + public ContentNode() + { } + // special ctor with no content data - for members public ContentNode(int id, Guid uid, IPublishedContentType contentType, int level, string path, int sortOrder, @@ -22,10 +25,10 @@ namespace Umbraco.Web.PublishedCache.NuCache Path = path; SortOrder = sortOrder; ParentContentId = parentContentId; + FirstChildContentId = -1; + NextSiblingContentId = -1; CreateDate = createDate; CreatorId = creatorId; - - ChildContentIds = new List(); } public ContentNode(int id, Guid uid, IPublishedContentType contentType, @@ -53,10 +56,10 @@ namespace Umbraco.Web.PublishedCache.NuCache Path = path; SortOrder = sortOrder; ParentContentId = parentContentId; + FirstChildContentId = -1; + NextSiblingContentId = -1; CreateDate = createDate; CreatorId = creatorId; - - ChildContentIds = new List(); } // two-phase ctor, phase 2 @@ -80,56 +83,29 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - // clone parent - private ContentNode(ContentNode origin, IUmbracoContextAccessor umbracoContextAccessor) + // clone + public ContentNode(ContentNode origin, IPublishedContentType contentType = null) { - // everything is the same, except for the child items - // list which is a clone of the original list - Id = origin.Id; Uid = origin.Uid; - ContentType = origin.ContentType; + ContentType = contentType ?? origin.ContentType; Level = origin.Level; Path = origin.Path; SortOrder = origin.SortOrder; ParentContentId = origin.ParentContentId; + FirstChildContentId = origin.FirstChildContentId; + NextSiblingContentId = origin.NextSiblingContentId; CreateDate = origin.CreateDate; CreatorId = origin.CreatorId; var originDraft = origin.DraftContent; var originPublished = origin.PublishedContent; + DraftContent = originDraft == null ? null : new PublishedContent(this, originDraft); + PublishedContent = originPublished == null ? null : new PublishedContent(this, originPublished); - DraftContent = originDraft == null ? null : new PublishedContent(this, originDraft, umbracoContextAccessor); - PublishedContent = originPublished == null ? null : new PublishedContent(this, originPublished, umbracoContextAccessor); DraftModel = DraftContent?.CreateModel(); PublishedModel = PublishedContent?.CreateModel(); - - ChildContentIds = new List(origin.ChildContentIds); // needs to be *another* list - } - - // clone with new content type - public ContentNode(ContentNode origin, IPublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) - { - Id = origin.Id; - Uid = origin.Uid; - ContentType = contentType; // change! - Level = origin.Level; - Path = origin.Path; - SortOrder = origin.SortOrder; - ParentContentId = origin.ParentContentId; - CreateDate = origin.CreateDate; - CreatorId = origin.CreatorId; - - var originDraft = origin.DraftContent; - var originPublished = origin.PublishedContent; - - DraftContent = originDraft == null ? null : new PublishedContent(this, originDraft.ContentData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); - DraftModel = DraftContent?.CreateModel(); - PublishedContent = originPublished == null ? null : new PublishedContent(this, originPublished.ContentData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); - PublishedModel = PublishedContent?.CreateModel(); - - ChildContentIds = origin.ChildContentIds; // can be the *same* list } // everything that is common to both draft and published versions @@ -141,7 +117,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public readonly string Path; public readonly int SortOrder; public readonly int ParentContentId; - public List ChildContentIds; + public int FirstChildContentId; + public int NextSiblingContentId; public readonly DateTime CreateDate; public readonly int CreatorId; @@ -155,23 +132,14 @@ namespace Umbraco.Web.PublishedCache.NuCache public IPublishedContent DraftModel; public IPublishedContent PublishedModel; - public ContentNode CloneParent( - IPublishedSnapshotAccessor publishedSnapshotAccessor, - IUmbracoContextAccessor umbracoContextAccessor) - { - return new ContentNode(this, umbracoContextAccessor); - } - public ContentNodeKit ToKit() - { - return new ContentNodeKit - { - Node = this, - ContentTypeId = ContentType.Id, + => new ContentNodeKit + { + Node = this, + ContentTypeId = ContentType.Id, - DraftData = DraftContent?.ContentData, - PublishedData = PublishedContent?.ContentData - }; - } + DraftData = DraftContent?.ContentData, + PublishedData = PublishedContent?.ContentData + }; } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 5693bd3204..5b54bb7345 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IVariationContextAccessor _variationContextAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ConcurrentDictionary> _contentNodes; - private readonly ConcurrentDictionary> _contentRootNodes; + private LinkedNode _root; private readonly ConcurrentDictionary> _contentTypesById; private readonly ConcurrentDictionary> _contentTypesByAlias; private readonly ConcurrentDictionary _xmap; @@ -60,7 +60,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _localDb = localDb; _contentNodes = new ConcurrentDictionary>(); - _contentRootNodes = new ConcurrentDictionary>(); + _root = new LinkedNode(new ContentNode(), 0); _contentTypesById = new ConcurrentDictionary>(); _contentTypesByAlias = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); _xmap = new ConcurrentDictionary(); @@ -176,7 +176,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } Rollback(_contentNodes); - Rollback(_contentRootNodes); + RollbackRoot(); Rollback(_contentTypesById); Rollback(_contentTypesByAlias); } @@ -202,6 +202,14 @@ namespace Umbraco.Web.PublishedCache.NuCache if (lockInfo.Taken) Monitor.Exit(_rlocko); } + private void RollbackRoot() + { + if (_root.Gen <= _liveGen) return; + + if (_root.Next != null) + _root = _root.Next; + } + private void Rollback(ConcurrentDictionary> dictionary) where TValue : class { @@ -289,7 +297,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (node == null) continue; var contentTypeId = node.ContentType.Id; if (index.TryGetValue(contentTypeId, out var contentType) == false) continue; - SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor)); + SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType)); } } finally @@ -347,7 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // perform update of content with refreshed content type - from the kits - // skip missing type, skip missing parents & unbuildable kits - what else could we do? + // skip missing type, skip missing parents & un-buildable kits - what else could we do? // kits are ordered by level, so ParentExits is ok here var visited = new List(); foreach (var kit in kits.Where(x => @@ -358,7 +366,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // replacing the node: must preserve the parents var node = GetHead(_contentNodes, kit.Node.Id)?.Value; if (node != null) - kit.Node.ChildContentIds = node.ChildContentIds; + kit.Node.FirstChildContentId = node.FirstChildContentId; SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); @@ -412,10 +420,10 @@ namespace Umbraco.Web.PublishedCache.NuCache foreach (var id in contentTypeNodes[contentType.Id]) { - _contentNodes.TryGetValue(id, out LinkedNode link); + _contentNodes.TryGetValue(id, out var link); if (link?.Value == null) continue; - var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor); + var node = new ContentNode(link.Value, contentType); SetValueLocked(_contentNodes, id, node); if (_localDb != null) RegisterChange(id, node.ToKit()); } @@ -455,7 +463,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private static LinkedNode GetHead(ConcurrentDictionary> dict, TKey key) where TValue : class { - dict.TryGetValue(key, out LinkedNode link); // else null + dict.TryGetValue(key, out var link); // else null return link; } @@ -464,7 +472,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // ReSharper disable LocalizableElement if (kit.IsEmpty) throw new ArgumentException("Kit is empty.", nameof(kit)); - if (kit.Node.ChildContentIds.Count > 0) + if (kit.Node.FirstChildContentId > 0) throw new ArgumentException("Kit content cannot have children.", nameof(kit)); // ReSharper restore LocalizableElement @@ -476,7 +484,7 @@ namespace Umbraco.Web.PublishedCache.NuCache Lock(lockInfo); // get existing - _contentNodes.TryGetValue(kit.Node.Id, out LinkedNode link); + _contentNodes.TryGetValue(kit.Node.Id, out var link); var existing = link?.Value; // else ignore, what else could we do? @@ -488,7 +496,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // manage children if (existing != null) - kit.Node.ChildContentIds = existing.ChildContentIds; + kit.Node.FirstChildContentId = existing.FirstChildContentId; // set SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); @@ -498,13 +506,13 @@ namespace Umbraco.Web.PublishedCache.NuCache if (existing == null) { // new, add to parent - AddToParentLocked(kit.Node); + AddNodeLocked(kit.Node); } else if (moving) { // moved, remove existing from its parent, add content to its parent - RemoveFromParentLocked(existing); - AddToParentLocked(kit.Node); + RemoveNodeLocked(existing); + AddNodeLocked(kit.Node); } _xmap[kit.Node.Uid] = kit.Node.Id; @@ -515,6 +523,14 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + private void ClearRootLocked() + { + if (_root.Gen < _liveGen) + _root = new LinkedNode(new ContentNode(), _liveGen, _root); + else + _root.Value.FirstChildContentId = -1; + } + // IMPORTANT kits must be sorted out by LEVEL public void SetAll(IEnumerable kits) { @@ -524,18 +540,18 @@ namespace Umbraco.Web.PublishedCache.NuCache Lock(lockInfo); ClearLocked(_contentNodes); - ClearLocked(_contentRootNodes); + ClearRootLocked(); // do NOT clear types else they are gone! //ClearLocked(_contentTypesById); //ClearLocked(_contentTypesByAlias); - // skip missing parents & unbuildable kits - what else could we do? + // skip missing parents & un-buildable kits - what else could we do? foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x))) { SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddToParentLocked(kit.Node); + AddNodeLocked(kit.Node); _xmap[kit.Node.Uid] = kit.Node.Id; } @@ -555,23 +571,23 @@ namespace Umbraco.Web.PublishedCache.NuCache Lock(lockInfo); // get existing - _contentNodes.TryGetValue(rootContentId, out LinkedNode link); + _contentNodes.TryGetValue(rootContentId, out var link); var existing = link?.Value; // clear if (existing != null) { ClearBranchLocked(existing); - RemoveFromParentLocked(existing); + RemoveNodeLocked(existing); } // now add them all back - // skip missing parents & unbuildable kits - what else could we do? + // skip missing parents & un-buildable kits - what else could we do? foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x))) { SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddToParentLocked(kit.Node); + AddNodeLocked(kit.Node); _xmap[kit.Node.Uid] = kit.Node.Id; } @@ -601,7 +617,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ClearBranchLocked(content); // manage the tree - RemoveFromParentLocked(content); + RemoveNodeLocked(content); return true; } @@ -626,10 +642,13 @@ namespace Umbraco.Web.PublishedCache.NuCache _xmap.TryRemove(content.Uid, out _); - foreach (var childId in content.ChildContentIds) + var id = content.FirstChildContentId; + while (id > 0) { - if (_contentNodes.TryGetValue(childId, out LinkedNode link) == false || link.Value == null) continue; + if (!_contentNodes.TryGetValue(id, out var link) || link.Value == null) + throw new Exception("panic: failed to get child"); ClearBranchLocked(link.Value); + id = link.Value.NextSiblingContentId; } } @@ -641,24 +660,37 @@ namespace Umbraco.Web.PublishedCache.NuCache return link; } - private void RemoveFromParentLocked(ContentNode content) + private void RemoveNodeLocked(ContentNode content) { - // remove from root content index, - // or parent's children index + LinkedNode parentLink; if (content.ParentContentId < 0) { - SetValueLocked(_contentRootNodes, content.Id, null); + parentLink = _root; } else { - // obviously parent has to exist - var link = GetParentLink(content); - var parent = link.Value; - if (link.Gen < _liveGen) - parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor); - parent.ChildContentIds.Remove(content.Id); - if (link.Gen < _liveGen) - SetValueLocked(_contentNodes, parent.Id, parent); + if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null) + throw new Exception("panic: failed to get parent"); + } + + var parent = parentLink.Value; + + if (parent.FirstChildContentId == content.Id) + { + parent = GenCloneLocked(parentLink); + parent.FirstChildContentId = content.NextSiblingContentId; + } + else + { + if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var link) || link.Value == null) + throw new Exception("panic: failed to get first child"); + + while (link.Value.NextSiblingContentId != content.Id) + if (!_contentNodes.TryGetValue(parent.NextSiblingContentId, out link) || link.Value == null) + throw new Exception("panic: failed to get next sibling"); + + var prevChild = GenCloneLocked(link); + prevChild.NextSiblingContentId = content.NextSiblingContentId; } } @@ -679,25 +711,90 @@ namespace Umbraco.Web.PublishedCache.NuCache return node?.PublishedModel != null; } - private void AddToParentLocked(ContentNode content) + private ContentNode GenCloneLocked(LinkedNode link) { - // add to root content index, - // or parent's children index + var node = link.Value; + + if (node != null && link.Gen < _liveGen) + { + node = new ContentNode(link.Value); + if (link == _root) + SetRootLocked(node); + else + SetValueLocked(_contentNodes, node.Id, node); + } + + return node; + } + + private void AddNodeLocked(ContentNode content) + { + LinkedNode parentLink; + if (content.ParentContentId < 0) { - // need an object reference... just use this... - SetValueLocked(_contentRootNodes, content.Id, this); + parentLink = _root; } else { - // assume parent has been validated and exists - var link = GetParentLink(content); - var parent = link.Value; - if (link.Gen < _liveGen) - parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor); - parent.ChildContentIds.Add(content.Id); - if (link.Gen < _liveGen) - SetValueLocked(_contentNodes, parent.Id, parent); + if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null) + throw new Exception("panic: failed to get parent"); + } + + var parent = parentLink.Value; + + if (parent.FirstChildContentId < 0) + { + parent = GenCloneLocked(parentLink); + parent.FirstChildContentId = content.Id; + return; + } + + if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var prevChildLink) || prevChildLink.Value == null) + throw new Exception("panic: failed to get first child"); + + var prevChild = prevChildLink.Value; + + if (prevChild.SortOrder > content.SortOrder) + { + content.NextSiblingContentId = parent.FirstChildContentId; + + parent = GenCloneLocked(parentLink); + parent.FirstChildContentId = content.Id; + return; + } + + while (prevChild.NextSiblingContentId > 0) + { + if (!_contentNodes.TryGetValue(prevChild.NextSiblingContentId, out var link) || link.Value == null) + throw new Exception("panic: failed to get next child"); + var nextChild = link.Value; + + if (nextChild.SortOrder > content.SortOrder) + { + content.NextSiblingContentId = nextChild.Id; + prevChild = GenCloneLocked(prevChildLink); + prevChild.NextSiblingContentId = content.Id; + return; + } + + prevChild = nextChild; + prevChildLink = link; + } + + prevChild = GenCloneLocked(prevChildLink); + prevChild.NextSiblingContentId = content.Id; + } + + private void SetRootLocked(ContentNode node) + { + if (_root.Gen != _liveGen) + { + _root = new LinkedNode(node, _liveGen, _root); + } + else + { + _root.Value = node; } } @@ -764,18 +861,24 @@ namespace Umbraco.Web.PublishedCache.NuCache public IEnumerable GetAtRoot(long gen) { - // look ma, no lock! - foreach (var kvp in _contentRootNodes) + var z = _root; + while (z != null) { - var link = kvp.Value; - while (link != null) - { - if (link.Gen <= gen) - break; - link = link.Next; - } - if (link?.Value != null) - yield return Get(kvp.Key, gen); + if (z.Gen <= gen) + break; + z = z.Next; + } + if (z == null) + yield break; + + var id = z.Value.FirstChildContentId; + + while (id > 0) + { + if (!_contentNodes.TryGetValue(id, out var link) || link == null) + throw new Exception("panic: failed to get sibling"); + yield return link.Value; + id = link.Value.NextSiblingContentId; } } @@ -928,7 +1031,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return _collectTask; // ReSharper disable InconsistentlySynchronizedField - var task = _collectTask = Task.Run(() => Collect()); + var task = _collectTask = Task.Run(Collect); _collectTask.ContinueWith(_ => { lock (_rlocko) @@ -957,11 +1060,19 @@ namespace Umbraco.Web.PublishedCache.NuCache } Collect(_contentNodes); - Collect(_contentRootNodes); + CollectRoot(); Collect(_contentTypesById); Collect(_contentTypesByAlias); } + private void CollectRoot() + { + var link = _root; + while (link.Next != null && link.Next.Gen > _floorGen) + link = link.Next; + link.Next = null; + } + private void Collect(ConcurrentDictionary> dict) where TValue : class { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index c1215c881b..3ededddba3 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -67,12 +67,9 @@ namespace Umbraco.Web.PublishedCache.NuCache return user?.Name; } - // (see ContentNode.CloneParent) - public PublishedContent( - ContentNode contentNode, - PublishedContent origin, - IUmbracoContextAccessor umbracoContextAccessor) - : base(umbracoContextAccessor) + // used when cloning in ContentNode + public PublishedContent(ContentNode contentNode, PublishedContent origin) + : base(origin.UmbracoContextAccessor) { _contentNode = contentNode; _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; @@ -124,43 +121,11 @@ namespace Umbraco.Web.PublishedCache.NuCache return GetContentByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); } - private IEnumerable GetContentByIds(bool previewing, IEnumerable ids) - { - var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; - - // beware! the loop below CANNOT be converted to query such as: - //return ids.Select(x => _getContentByIdFunc(publishedSnapshot, previewing, x)).Where(x => x != null); - // because it would capture the published snapshot and cause all sorts of issues - // - // we WANT to get the actual current published snapshot each time we run - - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var id in ids) - { - var content = GetContentByIdFunc(publishedSnapshot, previewing, id); - if (content != null) yield return content; - } - } - private IPublishedContent GetMediaById(bool previewing, int id) { return GetMediaByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); } - private IEnumerable GetMediaByIds(bool previewing, IEnumerable ids) - { - var publishedShapshot = _publishedSnapshotAccessor.PublishedSnapshot; - - // see note above for content - - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var id in ids) - { - var content = GetMediaByIdFunc(publishedShapshot, previewing, id); - if (content != null) yield return content; - } - } - #endregion #region Content Type @@ -343,43 +308,34 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override IEnumerable Children(string culture = null) { - // FIXME THIS CANNOT WORK - // we cannot cache children this way, they should be a linked list! - throw new NotImplementedException(); + Func getById; - var cache = GetAppropriateCache(); - if (cache == null || PublishedSnapshotService.CachePublishedContentChildren == false) - return GetChildren(); - - // note: ToArray is important here, we want to cache the result, not the function! - return (IEnumerable)cache.Get(ChildrenCacheKey, () => GetChildren().ToArray()); - } - - private string _childrenCacheKey; - - private string ChildrenCacheKey => _childrenCacheKey ?? (_childrenCacheKey = CacheKeys.PublishedContentChildren(Key, IsPreviewing)); - - private IEnumerable GetChildren() - { - IEnumerable c; - switch (_contentNode.ContentType.ItemType) + switch (ContentType.ItemType) { case PublishedItemType.Content: - c = GetContentByIds(IsPreviewing, _contentNode.ChildContentIds); + getById = GetContentByIdFunc; break; case PublishedItemType.Media: - c = GetMediaByIds(IsPreviewing, _contentNode.ChildContentIds); + getById = GetMediaByIdFunc; break; default: - throw new Exception("oops"); + throw new Exception("panic: invalid item type"); } - return c.OrderBy(x => x.SortOrder); + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; + var id = _contentNode.FirstChildContentId; - // notes: - // _contentNode.ChildContentIds is an unordered int[] - // needs to fetch & sort - do it only once, lazily, though - // Q: perfs-wise, is it better than having the store managed an ordered list + while (id > 0) + { + var content = (PublishedContent) getById(publishedSnapshot, IsPreviewing, id); + if (content == null) + throw new Exception("panic: failed to get content"); + + if (content.IsInvariantOrHasCulture(culture)) + yield return content; + + id = content._contentNode.NextSiblingContentId; + } } #endregion @@ -439,7 +395,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // used by navigable content // includes all children, published or unpublished // NavigableNavigator takes care of selecting those it wants - internal IList ChildIds => _contentNode.ChildContentIds; + // note: this is not efficient - we do not try to be (would require a double-linked list) + internal IList ChildIds => Children().Select(x => x.Id).ToList(); // used by Property // gets a value indicating whether the content or media exists in diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs index 20d7e7ddcd..78e0ec8d33 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs @@ -17,4 +17,4 @@ public volatile TValue Value; public volatile LinkedNode Next; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 15d4432a90..2fc449731d 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -172,7 +172,11 @@ namespace Umbraco.Web /// /// Culture is case-insensitive. public static bool HasCulture(this IPublishedContent content, string culture) - => content.Cultures.Contains(culture ?? string.Empty); + => content.Cultures.Contains(culture ?? string.Empty); // fixme oops?! + + // fixme + public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) + => !content.ContentType.VariesByCulture() || content.Cultures.Contains(culture ?? ""); /// /// Filters a sequence of to return invariant items, and items that are published for the specified culture. From 55d4457c70c1cf1f7ba24d6224d8b1192ccfa6bd Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 22 Apr 2019 18:14:03 +0200 Subject: [PATCH 017/218] UmbracoContext: don't use obsolete properties --- .../PublishedContentCacheTests.cs | 2 +- .../PublishedContentExtensionTests.cs | 10 +++--- .../PublishedContentLanguageVariantTests.cs | 34 +++++++++---------- .../PublishedContentMoreTests.cs | 18 +++++----- .../PublishedContent/PublishedContentTests.cs | 10 +++--- .../PublishedContent/RootNodeTests.cs | 6 ++-- .../Routing/RenderRouteHandlerTests.cs | 4 +-- src/Umbraco.Tests/Routing/UrlProviderTests.cs | 2 +- src/Umbraco.Tests/Routing/UrlRoutesTests.cs | 12 +++---- .../Routing/UrlsProviderWithDomainsTests.cs | 2 +- .../Routing/UrlsWithNestedDomains.cs | 2 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 8 ++--- .../Scoping/ScopedNuCacheTests.cs | 6 ++-- src/Umbraco.Tests/Scoping/ScopedXmlTests.cs | 6 ++-- .../config/splashes/NoNodes.aspx.cs | 2 +- .../Editors/MacroRenderingController.cs | 2 +- src/Umbraco.Web/Macros/MacroRenderer.cs | 2 +- .../Mvc/RedirectToUmbracoPageResult.cs | 2 +- .../Mvc/UmbracoVirtualNodeByIdRouteHandler.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 4 +-- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 2 +- .../Routing/ContentFinderByConfigured404.cs | 4 +-- .../Routing/ContentFinderByIdPath.cs | 2 +- .../Routing/ContentFinderByPageIdQuery.cs | 2 +- .../Routing/ContentFinderByRedirectUrl.cs | 2 +- src/Umbraco.Web/Routing/ContentFinderByUrl.cs | 2 +- .../Routing/ContentFinderByUrlAlias.cs | 2 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 6 ++-- src/Umbraco.Web/Routing/PublishedRouter.cs | 4 +-- .../Routing/RedirectTrackingComponent.cs | 4 +-- src/Umbraco.Web/Routing/UrlProvider.cs | 4 +-- src/Umbraco.Web/Templates/TemplateRenderer.cs | 2 +- .../Templates/TemplateUtilities.cs | 4 +-- src/Umbraco.Web/UmbracoComponentRenderer.cs | 2 +- src/Umbraco.Web/UmbracoInjectedModule.cs | 2 +- 35 files changed, 90 insertions(+), 90 deletions(-) diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs index 2a6739df38..11fb55f9b3 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs @@ -84,7 +84,7 @@ namespace Umbraco.Tests.Cache.PublishedCache globalSettings, new TestVariationContextAccessor()); - _cache = _umbracoContext.ContentCache; + _cache = _umbracoContext.Content; } [Test] diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs index acbad002ee..ced4c012f8 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.PublishedContent { InitializeInheritedContentTypes(); - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("inherited", false)); } @@ -36,7 +36,7 @@ namespace Umbraco.Tests.PublishedContent { InitializeInheritedContentTypes(); - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("base", false), Is.False); } @@ -45,7 +45,7 @@ namespace Umbraco.Tests.PublishedContent { InitializeInheritedContentTypes(); - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("inherited", true)); } @@ -55,7 +55,7 @@ namespace Umbraco.Tests.PublishedContent InitializeInheritedContentTypes(); ContentTypesCache.GetPublishedContentTypeByAlias = null; - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("base", true)); } @@ -64,7 +64,7 @@ namespace Umbraco.Tests.PublishedContent { InitializeInheritedContentTypes(); - var publishedContent = _ctx.ContentCache.GetById(1100); + var publishedContent = _ctx.Content.GetById(1100); Assert.That(publishedContent.IsDocumentType("invalidbase", true), Is.False); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 4fe028b1db..dc726c5748 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -191,7 +191,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Populated_Requested_Language() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "en-US"); Assert.AreEqual("Welcome", value); } @@ -199,7 +199,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Populated_Requested_Non_Default_Language() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "de"); Assert.AreEqual("Willkommen", value); } @@ -207,7 +207,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "fr"); Assert.IsNull(value); } @@ -215,7 +215,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Unless_Requested() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "es"); Assert.IsNull(value); } @@ -223,7 +223,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "es", fallback: Fallback.ToLanguage); Assert.AreEqual("Welcome", value); } @@ -231,7 +231,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); Assert.AreEqual("Welcome", value); } @@ -239,7 +239,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); var value = content.Value("welcomeText", "no", fallback: Fallback.ToLanguage); Assert.IsNull(value); } @@ -247,7 +247,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); var value = content.Value("welcomeText2"); Assert.IsNull(value); } @@ -255,7 +255,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); } @@ -263,7 +263,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested2() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First().Children().First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2"); Assert.IsNull(value); @@ -272,7 +272,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively2() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First().Children().First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); @@ -281,7 +281,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively3() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First().Children().First(); Assert.IsNull(content.GetProperty("noprop")); var value = content.Value("noprop", fallback: Fallback.ToAncestors); // property has no value but we still get the value (ie, the converter would do something) @@ -292,7 +292,7 @@ namespace Umbraco.Tests.PublishedContent public void Can_Get_Content_With_Recursive_Priority() { Current.VariationContextAccessor.VariationContext = new VariationContext("nl"); - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language)); @@ -303,7 +303,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_With_Fallback_Language_Priority() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.ToLanguage); // No Dutch value is directly assigned. Check has fallen back to English value from language variant. @@ -313,14 +313,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Throws_For_Non_Supported_Fallback() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); Assert.Throws(() => content.Value("welcomeText", "nl", fallback: Fallback.To(999))); } [Test] public void Can_Fallback_To_Default_Value() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); // no Dutch value is assigned, so getting null var value = content.Value("welcomeText", "nl"); @@ -338,7 +338,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Have_Custom_Default_Value() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); // HACK: the value, pretend the converter would return something var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 378671b81b..abd4771547 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -101,14 +101,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void First() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First(); Assert.AreEqual("Content 1", content.Name()); } [Test] public void Distinct() { - var items = Current.UmbracoContext.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.Content.GetAtRoot() .Distinct() .Distinct() .ToIndexedArray(); @@ -132,7 +132,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType1() { - var items = Current.UmbracoContext.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.Content.GetAtRoot() .OfType() .Distinct() .ToIndexedArray(); @@ -143,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType2() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.Content.GetAtRoot() .OfType() .Distinct() .ToIndexedArray(); @@ -154,7 +154,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void OfType() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.Content.GetAtRoot() .OfType() .First(x => x.Prop1 == 1234); Assert.AreEqual("Content 2", content.Name()); @@ -164,7 +164,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Position() { - var items = Current.UmbracoContext.ContentCache.GetAtRoot() + var items = Current.UmbracoContext.Content.GetAtRoot() .Where(x => x.Value("prop1") == 1234) .ToIndexedArray(); @@ -179,7 +179,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Issue() { - var content = Current.UmbracoContext.ContentCache.GetAtRoot() + var content = Current.UmbracoContext.Content.GetAtRoot() .Distinct() .OfType(); @@ -187,12 +187,12 @@ namespace Umbraco.Tests.PublishedContent var first = where.First(); Assert.AreEqual(1234, first.Prop1); - var content2 = Current.UmbracoContext.ContentCache.GetAtRoot() + var content2 = Current.UmbracoContext.Content.GetAtRoot() .OfType() .First(x => x.Prop1 == 1234); Assert.AreEqual(1234, content2.Prop1); - var content3 = Current.UmbracoContext.ContentCache.GetAtRoot() + var content3 = Current.UmbracoContext.Content.GetAtRoot() .OfType() .First(); Assert.AreEqual(1234, content3.Prop1); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 4472842251..fd0837b0ab 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -142,7 +142,7 @@ namespace Umbraco.Tests.PublishedContent internal IPublishedContent GetNode(int id) { var ctx = GetUmbracoContext("/test"); - var doc = ctx.ContentCache.GetById(id); + var doc = ctx.Content.GetById(id); Assert.IsNotNull(doc); return doc; } @@ -151,16 +151,16 @@ namespace Umbraco.Tests.PublishedContent public void GetNodeByIds() { var ctx = GetUmbracoContext("/test"); - var contentById = ctx.ContentCache.GetById(1173); + var contentById = ctx.Content.GetById(1173); Assert.IsNotNull(contentById); - var contentByGuid = ctx.ContentCache.GetById(_node1173Guid); + var contentByGuid = ctx.Content.GetById(_node1173Guid); Assert.IsNotNull(contentByGuid); Assert.AreEqual(contentById.Id, contentByGuid.Id); Assert.AreEqual(contentById.Key, contentByGuid.Key); - contentById = ctx.ContentCache.GetById(666); + contentById = ctx.Content.GetById(666); Assert.IsNull(contentById); - contentByGuid = ctx.ContentCache.GetById(Guid.NewGuid()); + contentByGuid = ctx.Content.GetById(Guid.NewGuid()); Assert.IsNull(contentByGuid); } diff --git a/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs b/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs index 0af30b4642..be94923ad0 100644 --- a/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs +++ b/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs @@ -13,17 +13,17 @@ namespace Umbraco.Tests.PublishedContent var ctx = GetUmbracoContext("/test"); // there is no content node with ID -1 - var content = ctx.ContentCache.GetById(-1); + var content = ctx.Content.GetById(-1); Assert.IsNull(content); // content at root has null parent - content = ctx.ContentCache.GetById(1046); + content = ctx.Content.GetById(1046); Assert.IsNotNull(content); Assert.AreEqual(1, content.Level); Assert.IsNull(content.Parent()); // non-existing content is null - content = ctx.ContentCache.GetById(666); + content = ctx.Content.GetById(666); Assert.IsNull(content); } diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index f1f38504f1..935417c510 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -100,7 +100,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("~/dummy-page", template.Id, routeData); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.PublishedContent = umbracoContext.ContentCache.GetById(1174); + frequest.PublishedContent = umbracoContext.Content.GetById(1174); frequest.TemplateModel = template; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); @@ -136,7 +136,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("~/dummy-page", template.Id, routeData, true); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.PublishedContent = umbracoContext.ContentCache.GetById(1172); + frequest.PublishedContent = umbracoContext.Content.GetById(1172); frequest.TemplateModel = template; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 38ef983120..3f39e4d4e8 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -78,7 +78,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(randomSample.Value, result); } - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); Assert.AreEqual(8, cachedRoutes.Count); diff --git a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs index 2e944211ca..4b8d708df6 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs @@ -200,7 +200,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var route = cache.GetRouteById(false, id); @@ -224,7 +224,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var route = cache.GetRouteById(false, id); @@ -238,7 +238,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var route = cache.GetRouteById(false, 1000); @@ -269,7 +269,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); const bool preview = false; // make sure we don't cache - but HOW? should be some sort of switch?! @@ -300,7 +300,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(hide); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); const bool preview = false; // make sure we don't cache - but HOW? should be some sort of switch?! @@ -323,7 +323,7 @@ DetermineRouteById(id): globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings.Object); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var content = cache.GetByRoute(false, "/a/b/c"); diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index 5a20d4da31..583d264f57 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -307,7 +307,7 @@ namespace Umbraco.Tests.Routing ignore = umbracoContext.UrlProvider.GetUrl(100111, false, current: new Uri("http://domain2.com")); ignore = umbracoContext.UrlProvider.GetUrl(1002, false, current: new Uri("http://domain2.com")); - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); Assert.AreEqual(7, cachedRoutes.Count); diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index 0eb621bd93..d50f2ee687 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("http://domain2.com/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, true)); // check that the proper route has been cached - var cache = umbracoContext.ContentCache as PublishedContentCache; + var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 9f57109329..46e78eb26b 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -184,11 +184,11 @@ namespace Umbraco.Tests.Runtimes var umbracoContext = umbracoContextReference.UmbracoContext; // assert that there is no published document - var pcontent = umbracoContext.ContentCache.GetById(content.Id); + var pcontent = umbracoContext.Content.GetById(content.Id); Assert.IsNull(pcontent); // but a draft document - pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + pcontent = umbracoContext.Content.GetById(true, content.Id); Assert.IsNotNull(pcontent); Assert.AreEqual("test", pcontent.Name()); Assert.IsTrue(pcontent.IsDraft()); @@ -202,7 +202,7 @@ namespace Umbraco.Tests.Runtimes contentService.Save(content); // assert that snapshot has been updated and there is now a published document - pcontent = umbracoContext.ContentCache.GetById(content.Id); + pcontent = umbracoContext.Content.GetById(content.Id); Assert.IsNotNull(pcontent); Assert.AreEqual("test", pcontent.Name()); Assert.IsFalse(pcontent.IsDraft()); @@ -211,7 +211,7 @@ namespace Umbraco.Tests.Runtimes Assert.AreEqual("/test/", pcontent.Url()); // and also an updated draft document - pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + pcontent = umbracoContext.Content.GetById(true, content.Id); Assert.IsNotNull(pcontent); Assert.AreEqual("testx", pcontent.Name()); Assert.IsTrue(pcontent.IsDraft()); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index f0efffc2cc..4f297b894b 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -148,7 +148,7 @@ namespace Umbraco.Tests.Scoping { evented++; - var e = umbracoContext.ContentCache.GetById(item.Id); + var e = umbracoContext.Content.GetById(item.Id); // during events, due to LiveSnapshot, we see the changes Assert.IsNotNull(e); @@ -162,7 +162,7 @@ namespace Umbraco.Tests.Scoping } // been created - var x = umbracoContext.ContentCache.GetById(item.Id); + var x = umbracoContext.Content.GetById(item.Id); Assert.IsNotNull(x); Assert.AreEqual("name", x.Name()); @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Scoping // after the scope, // if completed, we see the changes // else changes have been rolled back - x = umbracoContext.ContentCache.GetById(item.Id); + x = umbracoContext.Content.GetById(item.Id); Assert.IsNotNull(x); Assert.AreEqual(complete ? "changed" : "name", x.Name()); } diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 044965bc79..34482a79fa 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Scoping private static XmlStore XmlStore => (Current.Factory.GetInstance() as PublishedSnapshotService).XmlStore; private static XmlDocument XmlMaster => XmlStore.Xml; - private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.ContentCache).GetXml(false); + private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.Content).GetXml(false); [TestCase(true)] [TestCase(false)] @@ -85,7 +85,7 @@ namespace Umbraco.Tests.Scoping // sanity checks Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext); - Assert.AreSame(XmlStore, ((PublishedContentCache) umbracoContext.ContentCache).XmlStore); + Assert.AreSame(XmlStore, ((PublishedContentCache) umbracoContext.Content).XmlStore); // create document type, document var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" }; @@ -199,7 +199,7 @@ namespace Umbraco.Tests.Scoping // sanity checks Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext); - Assert.AreSame(XmlStore, ((PublishedContentCache)umbracoContext.ContentCache).XmlStore); + Assert.AreSame(XmlStore, ((PublishedContentCache)umbracoContext.Content).XmlStore); // create document type var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" }; diff --git a/src/Umbraco.Web.UI/config/splashes/NoNodes.aspx.cs b/src/Umbraco.Web.UI/config/splashes/NoNodes.aspx.cs index 38f6793fbe..51653d54ca 100644 --- a/src/Umbraco.Web.UI/config/splashes/NoNodes.aspx.cs +++ b/src/Umbraco.Web.UI/config/splashes/NoNodes.aspx.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.UI.Config.Splashes { base.OnInit(e); - var store = Current.UmbracoContext.ContentCache; + var store = Current.UmbracoContext.Content; if (store.HasContent()) { //if there is actually content, go to the root diff --git a/src/Umbraco.Web/Editors/MacroRenderingController.cs b/src/Umbraco.Web/Editors/MacroRenderingController.cs index 0c3b1626d0..d182a32ade 100644 --- a/src/Umbraco.Web/Editors/MacroRenderingController.cs +++ b/src/Umbraco.Web/Editors/MacroRenderingController.cs @@ -104,7 +104,7 @@ namespace Umbraco.Web.Editors if (m == null) throw new HttpResponseException(HttpStatusCode.NotFound); - var publishedContent = UmbracoContext.ContentCache.GetById(true, pageId); + var publishedContent = UmbracoContext.Content.GetById(true, pageId); //if it isn't supposed to be rendered in the editor then return an empty string //currently we cannot render a macro if the page doesn't yet exist diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 9736aa283b..61b2f0bda3 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -439,7 +439,7 @@ namespace Umbraco.Web.Macros // this was, and still is, an ugly piece of nonsense var value = string.Empty; - var cache = _umbracoContextAccessor.UmbracoContext.ContentCache; + var cache = _umbracoContextAccessor.UmbracoContext.Content; var splitpath = (string[])pageElements["splitpath"]; for (var i = splitpath.Length - 1; i > 0; i--) // at 0 we have root (-1) diff --git a/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs b/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs index 52d168620b..437eec4e5c 100644 --- a/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/RedirectToUmbracoPageResult.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.Mvc if (_publishedContent != null) return _publishedContent; //need to get the URL for the page - _publishedContent = Current.UmbracoContext.ContentCache.GetById(_pageId); + _publishedContent = Current.UmbracoContext.Content.GetById(_pageId); return _publishedContent; } diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeByIdRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeByIdRouteHandler.cs index 304f59b64b..0ada310b12 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeByIdRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeByIdRouteHandler.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Mvc protected sealed override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext) { - var byId = umbracoContext.ContentCache.GetById(_realNodeId); + var byId = umbracoContext.Content.GetById(_realNodeId); return byId == null ? null : FindContent(requestContext, umbracoContext, byId); } diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 2fc449731d..2e8e532a58 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -237,7 +237,7 @@ namespace Umbraco.Web .And() .ManagedQuery(term); - return query.Execute().ToPublishedSearchResults(Current.UmbracoContext.ContentCache); + return query.Execute().ToPublishedSearchResults(Current.UmbracoContext.Content); } public static IEnumerable SearchChildren(this IPublishedContent content, string term, string indexName = null) @@ -258,7 +258,7 @@ namespace Umbraco.Web .And() .ManagedQuery(term); - return query.Execute().ToPublishedSearchResults(Current.UmbracoContext.ContentCache); + return query.Execute().ToPublishedSearchResults(Current.UmbracoContext.Content); } #endregion diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index f1c755191e..354c315202 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Routing /// public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { - var node = umbracoContext.ContentCache.GetById(id); + var node = umbracoContext.Content.GetById(id); if (node == null) yield break; diff --git a/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs index e5bb23bfce..4019b0e917 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Routing while (pos > 1) { route = route.Substring(0, pos); - node = frequest.UmbracoContext.ContentCache.GetByRoute(route, culture: frequest?.Culture?.Name); + node = frequest.UmbracoContext.Content.GetByRoute(route, culture: frequest?.Culture?.Name); if (node != null) break; pos = route.LastIndexOf('/'); } @@ -71,7 +71,7 @@ namespace Umbraco.Web.Routing { _logger.Debug("Got id={ErrorNodeId}.", error404.Value); - content = frequest.UmbracoContext.ContentCache.GetById(error404.Value); + content = frequest.UmbracoContext.Content.GetById(error404.Value); _logger.Debug(content == null ? "Could not find content with that id." diff --git a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs index 8360ad7e38..8d2492f25c 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs @@ -50,7 +50,7 @@ namespace Umbraco.Web.Routing if (nodeId > 0) { _logger.Debug("Id={NodeId}", nodeId); - node = frequest.UmbracoContext.ContentCache.GetById(nodeId); + node = frequest.UmbracoContext.Content.GetById(nodeId); if (node != null) { diff --git a/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs index 54745b2529..70a920f6f0 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs @@ -14,7 +14,7 @@ int pageId; if (int.TryParse(frequest.UmbracoContext.HttpContext.Request["umbPageID"], out pageId)) { - var doc = frequest.UmbracoContext.ContentCache.GetById(pageId); + var doc = frequest.UmbracoContext.Content.GetById(pageId); if (doc != null) { diff --git a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs index d7b4c9925c..b8d7c0edd8 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByRedirectUrl.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Routing return false; } - var content = frequest.UmbracoContext.ContentCache.GetById(redirectUrl.ContentId); + var content = frequest.UmbracoContext.Content.GetById(redirectUrl.ContentId); var url = content == null ? "#" : content.Url(redirectUrl.Culture); if (url.StartsWith("#")) { diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrl.cs b/src/Umbraco.Web/Routing/ContentFinderByUrl.cs index 9aa52782d1..2dc665a00f 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrl.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.Routing Logger.Debug("Test route {Route}", route); - var node = docreq.UmbracoContext.ContentCache.GetByRoute(docreq.UmbracoContext.InPreviewMode, route, culture: docreq.Culture?.Name); + var node = docreq.UmbracoContext.Content.GetByRoute(docreq.UmbracoContext.InPreviewMode, route, culture: docreq.Culture?.Name); if (node != null) { docreq.PublishedContent = node; diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs index 60124a96a2..ce75cf64f6 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.Routing if (frequest.Uri.AbsolutePath != "/") // no alias if "/" { - node = FindContentByAlias(frequest.UmbracoContext.ContentCache, + node = FindContentByAlias(frequest.UmbracoContext.Content, frequest.HasDomain ? frequest.Domain.ContentId : 0, frequest.Culture.Name, frequest.Uri.GetAbsolutePathDecoded()); diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 842f855a0a..4e9944a808 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Routing if (!current.IsAbsoluteUri) throw new ArgumentException("Current url must be absolute.", nameof(current)); // will not use cache if previewing - var route = umbracoContext.ContentCache.GetRouteById(content.Id, culture); + var route = umbracoContext.Content.GetRouteById(content.Id, culture); return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); } @@ -78,7 +78,7 @@ namespace Umbraco.Web.Routing /// public virtual IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { - var node = umbracoContext.ContentCache.GetById(id); + var node = umbracoContext.Content.GetById(id); if (node == null) yield break; @@ -100,7 +100,7 @@ namespace Umbraco.Web.Routing var culture = d?.Culture?.Name; //although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok - var route = umbracoContext.ContentCache.GetRouteById(id, culture); + var route = umbracoContext.Content.GetRouteById(id, culture); if (route == null) continue; //need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs index 2e772cb175..086fd589ab 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -517,7 +517,7 @@ namespace Umbraco.Web.Routing { // try and get the redirect node from a legacy integer ID valid = true; - internalRedirectNode = request.UmbracoContext.ContentCache.GetById(internalRedirectId); + internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectId); } else { @@ -526,7 +526,7 @@ namespace Umbraco.Web.Routing { // try and get the redirect node from a UDI Guid valid = true; - internalRedirectNode = request.UmbracoContext.ContentCache.GetById(udiInternalRedirectId.Guid); + internalRedirectNode = request.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid); } } diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs index 4f9086e50b..9c6459da62 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs @@ -164,7 +164,7 @@ namespace Umbraco.Web.Routing { if (LockedEvents) return; - var contentCache = Current.UmbracoContext.ContentCache; + var contentCache = Current.UmbracoContext.Content; foreach (var entity in args.PublishedEntities) { var entityContent = contentCache.GetById(entity.Id); @@ -210,7 +210,7 @@ namespace Umbraco.Web.Routing private static void CreateRedirect(int contentId, string culture, Guid contentKey, string oldRoute) { - var contentCache = Current.UmbracoContext.ContentCache; + var contentCache = Current.UmbracoContext.Content; var newRoute = contentCache.GetRouteById(contentId, culture); if (IsNotRoute(newRoute) || oldRoute == newRoute) return; var redirectUrlService = Current.Services.RedirectUrlService; diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index f40aa00b01..d34123c96c 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -70,8 +70,8 @@ namespace Umbraco.Web.Routing #region GetUrl private UrlMode GetMode(bool absolute) => absolute ? UrlMode.Absolute : Mode; - private IPublishedContent GetDocument(int id) => _umbracoContext.ContentCache.GetById(id); - private IPublishedContent GetDocument(Guid id) => _umbracoContext.ContentCache.GetById(id); + private IPublishedContent GetDocument(int id) => _umbracoContext.Content.GetById(id); + private IPublishedContent GetDocument(Guid id) => _umbracoContext.Content.GetById(id); /// /// Gets the url of a published content. diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs index df711a084e..a7dcc6e422 100644 --- a/src/Umbraco.Web/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Templates // terribly much for this implementation since we are just creating a doc content request to modify it's properties manually. var contentRequest = _publishedRouter.CreateRequest(_umbracoContextAccessor.UmbracoContext); - var doc = contentRequest.UmbracoContext.ContentCache.GetById(pageId); + var doc = contentRequest.UmbracoContext.Content.GetById(pageId); if (doc == null) { diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 0be392c6dd..7c66bf6505 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -131,7 +131,7 @@ namespace Umbraco.Web.Templates public static string ResolveMediaFromTextString(string text) { // don't attempt to proceed without a context - if (Current.UmbracoContext == null || Current.UmbracoContext.MediaCache == null) + if (Current.UmbracoContext == null || Current.UmbracoContext.Media == null) { return text; } @@ -150,7 +150,7 @@ namespace Umbraco.Web.Templates { return match.Value; } - var media = Current.UmbracoContext.MediaCache.GetById(guidUdi.Guid); + var media = Current.UmbracoContext.Media.GetById(guidUdi.Guid); if(media == null) { // image does not exist - we could choose to remove the image entirely here (return empty string), diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index 25c2d78731..f6c3d30da3 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -92,7 +92,7 @@ namespace Umbraco.Web if (contentId == default) throw new ArgumentException("Invalid content id " + contentId); - var content = _umbracoContextAccessor.UmbracoContext.ContentCache?.GetById(true, contentId); + var content = _umbracoContextAccessor.UmbracoContext.Content?.GetById(true, contentId); if (content == null) throw new InvalidOperationException("Cannot render a macro, no content found by id " + contentId); diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 6a786ed6bf..c973fafa81 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -300,7 +300,7 @@ namespace Umbraco.Web // if yes, return true private bool EnsureHasContent(UmbracoContext context, HttpContextBase httpContext) { - if (context.ContentCache.HasContent()) + if (context.Content.HasContent()) return true; _logger.Warn("Umbraco has no content"); From 4feb766cc6ba27c008a84fa4a9a1c0c4b1f64358 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 22 Apr 2019 19:49:49 +0200 Subject: [PATCH 018/218] Fix IPublishedContent.Children() and NuCache --- .../PublishedContent/IPublishedContent.cs | 4 +- .../PublishedContentWrapped.cs | 2 +- .../DictionaryPublishedContent.cs | 2 +- .../XmlPublishedContent.cs | 2 +- .../Published/NestedContentTests.cs | 2 +- .../PublishedContentDataTableTests.cs | 2 +- .../SolidPublishedSnapshot.cs | 2 +- .../TestHelpers/Stubs/TestPublishedContent.cs | 2 +- .../PublishedContentHashtableConverter.cs | 2 +- .../Models/PublishedContentBase.cs | 4 +- .../PublishedCache/NuCache/ContentCache.cs | 23 +------ .../PublishedCache/NuCache/ContentNode.cs | 9 ++- .../PublishedCache/NuCache/ContentStore.cs | 4 +- ....DictionaryOfCultureVariationSerializer.cs | 5 +- ...Tree.DictionaryOfPropertyDataSerializer.cs | 5 +- .../NuCache/PublishedContent.cs | 67 ++++++++----------- .../NuCache/PublishedSnapshotService.cs | 4 +- .../PublishedCache/PublishedMember.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 25 ++----- 19 files changed, 64 insertions(+), 104 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index a6482b48ab..8ff648553c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -117,7 +117,7 @@ namespace Umbraco.Core.Models.PublishedContent /// the cultures that are published. For a draft content, those that are 'available' ie /// have a non-empty content name. /// - IReadOnlyList Cultures { get; } + IReadOnlyCollection Cultures { get; } /// /// Gets a value indicating whether the content is draft. @@ -164,8 +164,8 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets children that are available for the specified culture. /// Children are sorted by their sortOrder. + /// The '*' culture and supported and returns everything. /// - // FIXME: can culture be '*'? IEnumerable Children(string culture = null); #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 5bac22ad24..b2feec38c6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core.Models.PublishedContent public DateTime CultureDate(string culture = null) => _content.CultureDate(culture); /// - public IReadOnlyList Cultures => _content.Cultures; + public IReadOnlyCollection Cultures => _content.Cultures; /// public virtual bool IsDraft(string culture = null) => _content.IsDraft(culture); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index cd362cadc0..811851224c 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -157,7 +157,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // ReSharper disable once CollectionNeverUpdated.Local private static readonly List EmptyListOfString = new List(); - public override IReadOnlyList Cultures => EmptyListOfString; + public override IReadOnlyCollection Cultures => EmptyListOfString; public override string UrlSegment(string culture = null) => _urlName; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index bc9ab8010d..7d24eec1e9 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -140,7 +140,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // ReSharper disable once CollectionNeverUpdated.Local private static readonly List EmptyListOfString = new List(); - public override IReadOnlyList Cultures => EmptyListOfString; + public override IReadOnlyCollection Cultures => EmptyListOfString; public override string WriterName { diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 8b954d6ab0..5e024c2a72 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -285,7 +285,7 @@ namespace Umbraco.Tests.Published public override int SortOrder { get; } public override string Name(string culture = null) => default; public override DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public override IReadOnlyList Cultures => throw new NotSupportedException(); + public override IReadOnlyCollection Cultures => throw new NotSupportedException(); public override string UrlSegment(string culture = null) => default; public override string WriterName { get; } public override string CreatorName { get; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 077dfd3c94..e4bf02fec0 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -200,7 +200,7 @@ namespace Umbraco.Tests.PublishedContent public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public IReadOnlyList Cultures => throw new NotSupportedException(); + public IReadOnlyCollection Cultures => throw new NotSupportedException(); public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string WriterName { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index d61dead9c2..23aa93bb8d 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.PublishedContent public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public IReadOnlyList Cultures => throw new NotSupportedException(); + public IReadOnlyCollection Cultures => throw new NotSupportedException(); public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string WriterName { get; set; } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 8c631bb067..1fb090e220 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs // get return _cultures.TryGetValue(culture, out var date) ? date : DateTime.MinValue; } - public IReadOnlyList Cultures { get; set; } + public IReadOnlyCollection Cultures { get; set; } public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string DocumentTypeAlias => ContentType.Alias; diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index ebeafd2c06..cf2dca0b64 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -248,7 +248,7 @@ namespace Umbraco.Web.Macros private static readonly List EmptyListOfString = new List(); private IReadOnlyList _cultures; - public IReadOnlyList Cultures + public IReadOnlyCollection Cultures { get { diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index f29dc48d2b..086fae8b16 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Models /// /// This base class does which (a) consistently resolves and caches the Url, (b) provides an implementation /// for this[alias], and (c) provides basic content set management. - [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] + [DebuggerDisplay("Content Id: {Id}}")] public abstract class PublishedContentBase : IPublishedContent { protected PublishedContentBase(IUmbracoContextAccessor umbracoContextAccessor) @@ -138,7 +138,7 @@ namespace Umbraco.Web.Models public abstract DateTime CultureDate(string culture = null); /// - public abstract IReadOnlyList Cultures { get; } + public abstract IReadOnlyCollection Cultures { get; } /// public abstract bool IsDraft(string culture = null); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index e422c04f72..89de144403 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -246,28 +246,9 @@ namespace Umbraco.Web.PublishedCache.NuCache public override IEnumerable GetAtRoot(bool preview) { - if (PublishedSnapshotService.CacheContentCacheRoots == false) - return GetAtRootNoCache(preview); - - var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing - ? _elementsCache - : _snapshotCache; - - if (cache == null) - return GetAtRootNoCache(preview); - - // note: ToArray is important here, we want to cache the result, not the function! - return (IEnumerable)cache.Get( - CacheKeys.ContentCacheRoots(preview), - () => GetAtRootNoCache(preview).ToArray()); - } - - private IEnumerable GetAtRootNoCache(bool preview) - { - var c = _snapshot.GetAtRoot(); - // both .Draft and .Published cannot be null at the same time - return c.Select(n => GetNodePublishedContent(n, preview)).WhereNotNull().OrderBy(x => x.SortOrder); + // root is already sorted by sortOrder, and does not contain nulls + return _snapshot.GetAtRoot().Select(n => GetNodePublishedContent(n, preview)); } private static IPublishedContent GetNodePublishedContent(ContentNode node, bool preview) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index 58e60cd8ad..7c9a739448 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache.NuCache.DataSource; @@ -10,13 +11,17 @@ namespace Umbraco.Web.PublishedCache.NuCache { // special ctor for root pseudo node public ContentNode() - { } + { + FirstChildContentId = -1; + NextSiblingContentId = -1; + } // special ctor with no content data - for members public ContentNode(int id, Guid uid, IPublishedContentType contentType, int level, string path, int sortOrder, int parentContentId, DateTime createDate, int creatorId) + : this() { Id = id; Uid = uid; @@ -25,8 +30,6 @@ namespace Umbraco.Web.PublishedCache.NuCache Path = path; SortOrder = sortOrder; ParentContentId = parentContentId; - FirstChildContentId = -1; - NextSiblingContentId = -1; CreateDate = createDate; CreatorId = creatorId; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 5b54bb7345..9b1955fe43 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -508,7 +508,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // new, add to parent AddNodeLocked(kit.Node); } - else if (moving) + else if (moving || existing.SortOrder != kit.Node.SortOrder) { // moved, remove existing from its parent, add content to its parent RemoveNodeLocked(existing); @@ -562,7 +562,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - // IMPORTANT kits must be sorted out by LEVEL + // IMPORTANT kits must be sorted out by LEVEL and by SORT ORDER public void SetBranch(int rootContentId, IEnumerable kits) { var lockInfo = new WriteLockInfo(); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfCultureVariationSerializer.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfCultureVariationSerializer.cs index 958f6302fa..4521311302 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfCultureVariationSerializer.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfCultureVariationSerializer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using CSharpTest.Net.Serialization; using Umbraco.Core; @@ -14,7 +15,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource if (pcount == 0) return Empty; // read each variation - var dict = new Dictionary(); + var dict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); for (var i = 0; i < pcount; i++) { var languageId = PrimitiveSerializer.String.ReadFrom(stream); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs index 19c8beedb5..aa5dc9eb30 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using CSharpTest.Net.Serialization; using Umbraco.Core; @@ -9,7 +10,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource { public IDictionary ReadFrom(Stream stream) { - var dict = new Dictionary(); + var dict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); // read properties count var pcount = PrimitiveSerializer.Int32.ReadFrom(stream); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 3ededddba3..0a55049f9f 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -116,14 +116,18 @@ namespace Umbraco.Web.PublishedCache.NuCache internal static Func GetMediaByIdFunc { get; set; } = (publishedShapshot, previewing, id) => publishedShapshot.Media.GetById(previewing, id); - private IPublishedContent GetContentById(bool previewing, int id) + private Func GetGetterById(PublishedItemType itemType) { - return GetContentByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); - } + switch (ContentType.ItemType) + { + case PublishedItemType.Content: + return GetContentByIdFunc; + case PublishedItemType.Media: + return GetMediaByIdFunc; + default: + throw new Exception("panic: invalid item type"); + } - private IPublishedContent GetMediaById(bool previewing, int id) - { - return GetMediaByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); } #endregion @@ -226,17 +230,17 @@ namespace Umbraco.Web.PublishedCache.NuCache // ReSharper disable once CollectionNeverUpdated.Local private static readonly List EmptyListOfString = new List(); - private IReadOnlyList _cultures; + private IReadOnlyCollection _cultures; /// - public override IReadOnlyList Cultures + public override IReadOnlyCollection Cultures { get { if (!ContentType.VariesByCulture()) return EmptyListOfString; - return _cultures ?? (_cultures = ContentData.CultureInfos.Keys.ToList()); + return _cultures ?? (_cultures = new HashSet(ContentData.CultureInfos.Keys, StringComparer.OrdinalIgnoreCase)); } } @@ -292,49 +296,36 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override IPublishedContent Parent() { - // have to use the "current" cache because a PublishedContent can be shared - // amongst many snapshots and other content depend on the snapshots - switch (_contentNode.ContentType.ItemType) - { - case PublishedItemType.Content: - return GetContentById(IsPreviewing, _contentNode.ParentContentId); - case PublishedItemType.Media: - return GetMediaById(IsPreviewing, _contentNode.ParentContentId); - default: - throw new Exception($"Panic: unsupported item type \"{_contentNode.ContentType.ItemType}\"."); - } + var getById = GetGetterById(ContentType.ItemType); + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; + return getById(publishedSnapshot, IsPreviewing, ParentId); } /// public override IEnumerable Children(string culture = null) { - Func getById; + // invariant has invariant value (whatever the requested culture) + if (!ContentType.VariesByCulture() && culture != "*") + culture = ""; - switch (ContentType.ItemType) - { - case PublishedItemType.Content: - getById = GetContentByIdFunc; - break; - case PublishedItemType.Media: - getById = GetMediaByIdFunc; - break; - default: - throw new Exception("panic: invalid item type"); - } + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + var getById = GetGetterById(ContentType.ItemType); var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; var id = _contentNode.FirstChildContentId; while (id > 0) { - var content = (PublishedContent) getById(publishedSnapshot, IsPreviewing, id); + var content = getById(publishedSnapshot, IsPreviewing, id); if (content == null) throw new Exception("panic: failed to get content"); - if (content.IsInvariantOrHasCulture(culture)) + if (culture == "*" || content.IsInvariantOrHasCulture(culture)) yield return content; - id = content._contentNode.NextSiblingContentId; + id = UnwrapIPublishedContent(content)._contentNode.NextSiblingContentId; } } @@ -422,11 +413,9 @@ namespace Umbraco.Web.PublishedCache.NuCache // used by Navigable.Source,... internal static PublishedContent UnwrapIPublishedContent(IPublishedContent content) { - PublishedContentWrapped wrapped; - while ((wrapped = content as PublishedContentWrapped) != null) + while (content is PublishedContentWrapped wrapped) content = wrapped.Unwrap(); - var inner = content as PublishedContent; - if (inner == null) + if (!(content is PublishedContent inner)) throw new InvalidOperationException("Innermost content is not PublishedContent."); return inner; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index ce19764fb6..7c8b69f9b2 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -649,7 +649,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (capture.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) { // ?? should we do some RV check here? - // IMPORTANT GetbranchContentSources sorts kits by level + // IMPORTANT GetbranchContentSources sorts kits by level and by sort order var kits = _dataSource.GetBranchContentSources(scope, capture.Id); _contentStore.SetBranch(capture.Id, kits); } @@ -741,7 +741,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (capture.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) { // ?? should we do some RV check here? - // IMPORTANT GetbranchContentSources sorts kits by level + // IMPORTANT GetbranchContentSources sorts kits by level and by sort order var kits = _dataSource.GetBranchMediaSources(scope, capture.Id); _mediaStore.SetBranch(capture.Id, kits); } diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index f0c3ac4f5b..eec973c182 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -142,7 +142,7 @@ namespace Umbraco.Web.PublishedCache public override DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public override IReadOnlyList Cultures => throw new NotSupportedException(); + public override IReadOnlyCollection Cultures => throw new NotSupportedException(); public override string UrlSegment(string culture = null) => throw new NotSupportedException(); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 2e8e532a58..b31527eb57 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -172,9 +172,12 @@ namespace Umbraco.Web /// /// Culture is case-insensitive. public static bool HasCulture(this IPublishedContent content, string culture) - => content.Cultures.Contains(culture ?? string.Empty); // fixme oops?! + => content.Cultures.Contains(culture ?? string.Empty); - // fixme + /// + /// Determines whether the content is invariant, or has a culture. + /// + /// Culture is case-insensitive. public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) => !content.ContentType.VariesByCulture() || content.Cultures.Contains(culture ?? ""); @@ -895,24 +898,6 @@ namespace Umbraco.Web #region Axes: children - // FIXME: kill that one - /// - /// Gets the children of the content. - /// - /// The content. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The children of the content. - /// - /// Children are sorted by their sortOrder. - /// This method exists for consistency, it is the same as calling content.Children as a property. - /// - public static IEnumerable Children(this IPublishedContent content, string culture = null) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - - return content.Children(culture); //.WhereIsInvariantOrHasCulture(culture); - } - /// /// Gets the children of the content, filtered by a predicate. /// From a56f3b23d13de1370eb201e04028757831f083cd Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 22 Apr 2019 11:58:51 +0200 Subject: [PATCH 019/218] Cleanup PublishedMember --- src/Umbraco.Web/Models/PublishedProperty.cs | 58 ------------------- .../PublishedCache/PublishedMember.cs | 5 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 3 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 src/Umbraco.Web/Models/PublishedProperty.cs diff --git a/src/Umbraco.Web/Models/PublishedProperty.cs b/src/Umbraco.Web/Models/PublishedProperty.cs deleted file mode 100644 index f715df7450..0000000000 --- a/src/Umbraco.Web/Models/PublishedProperty.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Models -{ - public static class PublishedProperty - { - /// - /// Maps a collection of Property to a collection of IPublishedProperty for a specified collection of PublishedPropertyType. - /// - /// The published property types. - /// The properties. - /// A mapping function. - /// A collection of IPublishedProperty corresponding to the collection of PublishedPropertyType - /// and taking values from the collection of Property. - /// Ensures that all conversions took place correctly. - internal static IEnumerable MapProperties( - IEnumerable propertyTypes, IEnumerable properties, - Func map) - { - var propertyEditors = Current.PropertyEditors; - var dataTypeService = Current.Services.DataTypeService; - - // TODO: not dealing with variants - // but the entire thing should die anyways - - return propertyTypes.Select(x => - { - var p = properties.SingleOrDefault(xx => xx.Alias == x.Alias); - var v = p == null || p.GetValue() == null ? null : p.GetValue(); - if (v != null) - { - var e = propertyEditors[x.EditorAlias]; - - // We are converting to string, even for database values which are integer or - // DateTime, which is not optimum. Doing differently would require that we have a way to tell - // whether the conversion to XML string changes something or not... which we don't, and we - // don't want to implement it as PropertyValueEditor.ConvertDbToXml/String should die anyway. - - // Don't think about improving the situation here: this is a corner case and the real - // thing to do is to get rig of PropertyValueEditor.ConvertDbToXml/String. - - // Use ConvertDbToString to keep it simple, although everywhere we use ConvertDbToXml and - // nothing ensures that the two methods are consistent. - - if (e != null) - v = e.GetValueEditor().ConvertDbToString(p.PropertyType, v, dataTypeService); - } - - return map(x, v); - }); - } - } -} diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index eec973c182..08e00c1f17 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -35,16 +35,13 @@ namespace Umbraco.Web.PublishedCache // if they are not part of the member type properties - in which case they are created as // simple raw properties - which are completely invariant - var _properties = PublishedProperty.MapProperties(_publishedMemberType.PropertyTypes, _member.Properties, - (t, v) => new RawValueProperty(t, this, v ?? string.Empty)); - var properties = new List(); foreach (var propertyType in _publishedMemberType.PropertyTypes) { var property = _member.Properties[propertyType.Alias]; if (property == null) continue; - //properties.Add(new FooProperty(propertyType, this, property.Values)); + properties.Add(new RawValueProperty(propertyType, this, property.GetValue())); } EnsureMemberProperties(properties); _properties = properties.ToArray(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 28337e6d2c..a190d6711c 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -775,7 +775,6 @@ - From eb390f8d80eb17ee905157ecf5e7380b3e7876a0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 24 Apr 2019 09:32:49 +0200 Subject: [PATCH 020/218] Fix merge --- .../Routing/MediaUrlProviderTests.cs | 10 +-- .../Routing/UrlsProviderWithDomainsTests.cs | 52 ++++++----- .../Routing/UrlsWithNestedDomains.cs | 3 +- src/Umbraco.Web/PublishedElementExtensions.cs | 44 ++++++++++ .../Routing/DefaultMediaUrlProvider.cs | 12 +-- src/Umbraco.Web/Routing/IMediaUrlProvider.cs | 2 +- src/Umbraco.Web/Routing/UrlProvider.cs | 88 +++---------------- src/Umbraco.Web/UmbracoContext.cs | 4 +- 8 files changed, 100 insertions(+), 115 deletions(-) diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 5de99fdd38..268e4e9d85 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlMode.Auto, null, null); Assert.AreEqual(expected, resolvedUrl); } @@ -64,7 +64,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlMode.Auto, null, null); Assert.AreEqual(expected, resolvedUrl); } @@ -78,7 +78,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, mediaUrl, null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Absolute, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlMode.Absolute, null, null); Assert.AreEqual(expected, resolvedUrl); } @@ -89,7 +89,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.Boolean, "0", null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "test", UrlProviderMode.Absolute, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "test", UrlMode.Absolute, null, null); Assert.AreEqual(string.Empty, resolvedUrl); } @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Routing var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, "da", null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlMode.Auto, "da", null); Assert.AreEqual(daMediaUrl, resolvedUrl); } diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index 583d264f57..0a34fb8041 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -189,7 +189,8 @@ namespace Umbraco.Tests.Routing SetDomains1(); var currentUri = new Uri(currentUrl); - var result = umbracoContext.UrlProvider.GetUrl(nodeId, absolute, current: currentUri); + var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; + var result = umbracoContext.UrlProvider.GetUrl(nodeId, mode, current: currentUri); Assert.AreEqual(expected, result); } @@ -221,7 +222,8 @@ namespace Umbraco.Tests.Routing SetDomains2(); var currentUri = new Uri(currentUrl); - var result = umbracoContext.UrlProvider.GetUrl(nodeId, absolute, current : currentUri); + var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; + var result = umbracoContext.UrlProvider.GetUrl(nodeId, mode, current : currentUri); Assert.AreEqual(expected, result); } @@ -245,7 +247,8 @@ namespace Umbraco.Tests.Routing SetDomains3(); var currentUri = new Uri(currentUrl); - var result = umbracoContext.UrlProvider.GetUrl(nodeId, absolute, current : currentUri); + var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; + var result = umbracoContext.UrlProvider.GetUrl(nodeId, mode, current : currentUri); Assert.AreEqual(expected, result); } @@ -275,7 +278,8 @@ namespace Umbraco.Tests.Routing SetDomains4(); var currentUri = new Uri(currentUrl); - var result = umbracoContext.UrlProvider.GetUrl(nodeId, absolute, current : currentUri); + var mode = absolute ? UrlMode.Absolute : UrlMode.Auto; + var result = umbracoContext.UrlProvider.GetUrl(nodeId, mode, current : currentUri); Assert.AreEqual(expected, result); } @@ -295,17 +299,17 @@ namespace Umbraco.Tests.Routing SetDomains4(); string ignore; - ignore = umbracoContext.UrlProvider.GetUrl(1001, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(10011, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(100111, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(10012, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(100121, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(10013, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(1002, false, current: new Uri("http://domain1.com")); - ignore = umbracoContext.UrlProvider.GetUrl(1001, false, current: new Uri("http://domain2.com")); - ignore = umbracoContext.UrlProvider.GetUrl(10011, false, current: new Uri("http://domain2.com")); - ignore = umbracoContext.UrlProvider.GetUrl(100111, false, current: new Uri("http://domain2.com")); - ignore = umbracoContext.UrlProvider.GetUrl(1002, false, current: new Uri("http://domain2.com")); + ignore = umbracoContext.UrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com")); + ignore = umbracoContext.UrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = umbracoContext.UrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain2.com")); + ignore = umbracoContext.UrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain2.com")); var cache = umbracoContext.Content as PublishedContentCache; if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); @@ -324,15 +328,15 @@ namespace Umbraco.Tests.Routing CheckRoute(cachedRoutes, cachedIds, 1002, "/1002"); // use the cache - Assert.AreEqual("/", umbracoContext.UrlProvider.GetUrl(1001, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/en/", umbracoContext.UrlProvider.GetUrl(10011, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/en/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/fr/", umbracoContext.UrlProvider.GetUrl(10012, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/fr/1001-2-1/", umbracoContext.UrlProvider.GetUrl(100121, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/1001-3/", umbracoContext.UrlProvider.GetUrl(10013, false, current: new Uri("http://domain1.com"))); - Assert.AreEqual("/1002/", umbracoContext.UrlProvider.GetUrl(1002, false, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/", umbracoContext.UrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/en/", umbracoContext.UrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/en/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/fr/", umbracoContext.UrlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/fr/1001-2-1/", umbracoContext.UrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/1001-3/", umbracoContext.UrlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com"))); + Assert.AreEqual("/1002/", umbracoContext.UrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com"))); - Assert.AreEqual("http://domain1.com/fr/1001-2-1/", umbracoContext.UrlProvider.GetUrl(100121, false, current: new Uri("http://domain2.com"))); + Assert.AreEqual("http://domain1.com/fr/1001-2-1/", umbracoContext.UrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain2.com"))); } private static void CheckRoute(IDictionary routes, IDictionary ids, int id, string route) @@ -381,7 +385,7 @@ namespace Umbraco.Tests.Routing SetDomains5(); - var url = umbracoContext.UrlProvider.GetUrl(100111, true); + var url = umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Absolute); Assert.AreEqual("http://domain1.com/en/1001-1-1/", url); var result = umbracoContext.UrlProvider.GetOtherUrls(100111).ToArray(); diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index d50f2ee687..6587b2e4f6 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; using Umbraco.Core.Services; @@ -44,7 +45,7 @@ namespace Umbraco.Tests.Routing { new DefaultUrlProvider(settings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings:globalSettings.Object); - Assert.AreEqual("http://domain2.com/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, true)); + Assert.AreEqual("http://domain2.com/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111, UrlMode.Absolute)); // check that the proper route has been cached var cache = umbracoContext.Content as PublishedContentCache; diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs index de7c72d21a..49df6b2685 100644 --- a/src/Umbraco.Web/PublishedElementExtensions.cs +++ b/src/Umbraco.Web/PublishedElementExtensions.cs @@ -188,5 +188,49 @@ namespace Umbraco.Web } #endregion + + #region MediaUrl + + /// + /// Gets the url for the media. + /// + /// The content. + /// The property alias to resolve the url from. + /// The variation language. + /// The url for the content. + /// Better use the GetMediaUrl method but that method is here to complement MediaUrlAbsolute(). + public static string MediaUrl(this IPublishedContent content, string propertyAlias, string culture = null) + { + var umbracoContext = Composing.Current.UmbracoContext; + + if (umbracoContext == null) + throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null."); + if (umbracoContext.UrlProvider == null) + throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null."); + + return umbracoContext.UrlProvider.GetMediaUrl(content, propertyAlias, culture); + } + + /// + /// Gets the absolute url for the media. + /// + /// The content. + /// The property alias to resolve the url from. + /// The url mode. + /// The variation language. + /// The absolute url for the media. + public static string MediaUrl(this IPublishedContent content, string propertyAlias, UrlMode mode, string culture = null) + { + var umbracoContext = Composing.Current.UmbracoContext; + + if (umbracoContext == null) + throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null."); + if (umbracoContext.UrlProvider == null) + throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null."); + + return umbracoContext.UrlProvider.GetMediaUrl(content, propertyAlias, mode, culture); + } + + #endregion } } diff --git a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs index a9c961c280..18d10e577d 100644 --- a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Routing /// public virtual UrlInfo GetMediaUrl(UmbracoContext umbracoContext, IPublishedContent content, string propertyAlias, - UrlProviderMode mode, string culture, Uri current) + UrlMode mode, string culture, Uri current) { var prop = content.GetProperty(propertyAlias); var value = prop?.GetValue(culture); @@ -40,7 +40,7 @@ namespace Umbraco.Web.Routing return url == null ? null : UrlInfo.Url(url.ToString(), culture); } - private Uri AssembleUrl(string path, Uri current, UrlProviderMode mode) + private Uri AssembleUrl(string path, Uri current, UrlMode mode) { if (string.IsNullOrEmpty(path)) return null; @@ -48,15 +48,15 @@ namespace Umbraco.Web.Routing Uri uri; if (current == null) - mode = UrlProviderMode.Relative; // best we can do + mode = UrlMode.Relative; // best we can do switch (mode) { - case UrlProviderMode.Absolute: + case UrlMode.Absolute: uri = new Uri(current?.GetLeftPart(UriPartial.Authority) + path); break; - case UrlProviderMode.Relative: - case UrlProviderMode.Auto: + case UrlMode.Relative: + case UrlMode.Auto: uri = new Uri(path, UriKind.Relative); break; default: diff --git a/src/Umbraco.Web/Routing/IMediaUrlProvider.cs b/src/Umbraco.Web/Routing/IMediaUrlProvider.cs index 419e4d78df..8a81b27415 100644 --- a/src/Umbraco.Web/Routing/IMediaUrlProvider.cs +++ b/src/Umbraco.Web/Routing/IMediaUrlProvider.cs @@ -26,6 +26,6 @@ namespace Umbraco.Web.Routing /// e.g. a cdn url provider will most likely always return an absolute url. /// If the provider is unable to provide a url, it returns null. /// - UrlInfo GetMediaUrl(UmbracoContext umbracoContext, IPublishedContent content, string propertyAlias, UrlProviderMode mode, string culture, Uri current); + UrlInfo GetMediaUrl(UmbracoContext umbracoContext, IPublishedContent content, string propertyAlias, UrlMode mode, string culture, Uri current); } } diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index 10dde3bbae..0f79d11364 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -79,31 +79,6 @@ namespace Umbraco.Web.Routing private IPublishedContent GetDocument(int id) => _umbracoContext.Content.GetById(id); private IPublishedContent GetDocument(Guid id) => _umbracoContext.Content.GetById(id); - /// - /// Gets the url of a published content. - /// - /// The published content. - /// A culture. - /// The current absolute url. - /// The url for the published content. - public string GetUrl(IPublishedContent content, string culture = null, Uri current = null) - => GetUrl(content, Mode, culture, current); - - /// - /// Gets the url of a published content. - /// - /// The published content. - /// A value indicating whether the url should be absolute in any case. - /// A culture. - /// The current absolute url. - /// The url for the published content. - /// - /// The url is absolute or relative depending on Mode and on current, unless - /// absolute is true, in which case the url is always absolute. - /// - public string GetUrl(IPublishedContent content, bool absolute, string culture = null, Uri current = null) - => GetUrl(content, GetMode(absolute), culture, current); - /// /// Gets the url of a published content. /// @@ -114,21 +89,6 @@ namespace Umbraco.Web.Routing public string GetUrl(Guid id, string culture = null, Uri current = null) => GetUrl(GetDocument(id), Mode, culture, current); - /// - /// Gets the url of a published content. - /// - /// The published content identifier. - /// A value indicating whether the url should be absolute in any case. - /// A culture. - /// The current absolute url. - /// The url for the published content. - /// - /// The url is absolute or relative depending on Mode and on current, unless - /// absolute is true, in which case the url is always absolute. - /// - public string GetUrl(Guid id, bool absolute, string culture = null, Uri current = null) - => GetUrl(GetDocument(id), GetMode(absolute), culture, current); - /// /// Gets the url of a published content. /// @@ -150,21 +110,6 @@ namespace Umbraco.Web.Routing public string GetUrl(int id, string culture = null, Uri current = null) => GetUrl(GetDocument(id), Mode, culture, current); - /// - /// Gets the url of a published content. - /// - /// The published content identifier. - /// A value indicating whether the url should be absolute in any case. - /// A culture. - /// The current absolute url. - /// The url for the published content. - /// - /// The url is absolute or relative depending on Mode and on current, unless - /// absolute is true, in which case the url is always absolute. - /// - public string GetUrl(int id, bool absolute, string culture = null, Uri current = null) - => GetUrl(GetDocument(id), GetMode(absolute), culture, current); - /// /// Gets the url of a published content. /// @@ -176,6 +121,16 @@ namespace Umbraco.Web.Routing public string GetUrl(int id, UrlMode mode, string culture = null, Uri current = null) => GetUrl(GetDocument(id), mode, culture, current); + /// + /// Gets the url of a published content. + /// + /// The published content. + /// A culture. + /// The current absolute url. + /// The url for the published content. + public string GetUrl(IPublishedContent content, string culture = null, Uri current = null) + => GetUrl(content, Mode, culture, current); + /// /// Gets the url of a published content. /// @@ -273,7 +228,7 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it returns . /// - public string GetMediaUrl(IPublishedContent content, string propertyAlias, UrlMode mode = UrlMode.Wtf, string culture = null, Uri current = null) + public string GetMediaUrl(IPublishedContent content, string propertyAlias, string culture = null, Uri current = null) => GetMediaUrl(content, propertyAlias, Mode, culture, current); /// @@ -281,23 +236,6 @@ namespace Umbraco.Web.Routing /// /// The published content. /// The property alias to resolve the url from. - /// A value indicating whether the url should be absolute in any case. - /// The variation language. - /// The current absolute url. - /// The url for the media. - /// - /// The url is absolute or relative depending on mode and on current. - /// If the media is multi-lingual, gets the url for the specified culture or, - /// when no culture is specified, the current culture. - /// If the provider is unable to provide a url, it returns . - /// - public string GetMediaUrl(IPublishedContent content, string propertyAlias, boolx absolute, string culture = null, Uri current = null) - => GetMediaUrl(content, propertyAlias, GetMode(absolute), culture, current); - /// - /// Gets the url of a media item. - /// - /// The published content. - /// The property alias to resolve the url from. /// The url mode. /// The variation language. /// The current absolute url. @@ -308,9 +246,7 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it returns . /// - public string GetMediaUrl(IPublishedContent content, - string propertyAlias, UrlMode mode = UrlMode.Wtf, - string culture = null, Uri current = null) + public string GetMediaUrl(IPublishedContent content, string propertyAlias, UrlMode mode, string culture = null, Uri current = null) { if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 69b9ec1db9..3947adc0e5 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -245,7 +245,7 @@ namespace Umbraco.Web [Obsolete("Use the Url() method with UrlMode.Absolute.")] public string UrlAbsolute(int contentId, string culture = null) { - return UrlProvider.GetUrl(contentId, true, culture); + return UrlProvider.GetUrl(contentId, UrlMode.Absolute, culture); } /// @@ -257,7 +257,7 @@ namespace Umbraco.Web [Obsolete("Use the Url() method with UrlMode.Absolute.")] public string UrlAbsolute(Guid contentId, string culture = null) { - return UrlProvider.GetUrl(contentId, true, culture); + return UrlProvider.GetUrl(contentId, UrlMode.Absolute, culture); } #endregion From 25b8c8a5652d396f98e525a17fbe11d7926ba4d6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 24 Apr 2019 11:53:35 +0200 Subject: [PATCH 021/218] Refactor getting Urls --- .../PublishedContent/IPublishedContent.cs | 4 ++ .../Models/PublishedContent/UrlMode.cs | 4 ++ .../Models/PublishedContentBase.cs | 2 +- src/Umbraco.Web/PublishedElementExtensions.cs | 43 ++++-------- src/Umbraco.Web/Routing/UrlProvider.cs | 68 ++++--------------- src/Umbraco.Web/UmbracoContext.cs | 4 +- 6 files changed, 37 insertions(+), 88 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 8ff648553c..6917538331 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -97,6 +97,10 @@ namespace Umbraco.Core.Models.PublishedContent /// Gets the url of the content item. /// /// + /// If the content item is a document, then this method returns the url of the + /// document. If it is a media, then this methods return the media url for the + /// 'umbracoFile' property. Use the MediaUrl() method to get the media url for other + /// properties. /// The value of this property is contextual. It depends on the 'current' request uri, /// if any. In addition, when the content type is multi-lingual, this is the url for the /// specified culture. Otherwise, it is the invariant url. diff --git a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs index f19f93bec1..4cd6a680f4 100644 --- a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs +++ b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs @@ -5,6 +5,10 @@ /// public enum UrlMode { + /// + /// Indicates that the url provider should do what it has been configured to do. + /// + Default = 0, /// /// Indicates that the url provider should produce relative urls exclusively. diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 4fc08f6ff3..25c84c97e5 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -95,7 +95,7 @@ namespace Umbraco.Web.Models return umbracoContext.UrlProvider.GetUrl(this, mode, culture); case PublishedItemType.Media: - return umbracoContext.UrlProvider.GetMediaUrl(this, Constants.Conventions.Media.File, mode, culture); + return umbracoContext.UrlProvider.GetMediaUrl(this, mode, culture, Constants.Conventions.Media.File); default: throw new NotSupportedException(); diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs index 49df6b2685..2de8259d3c 100644 --- a/src/Umbraco.Web/PublishedElementExtensions.cs +++ b/src/Umbraco.Web/PublishedElementExtensions.cs @@ -145,7 +145,7 @@ namespace Umbraco.Web } #endregion - + #region ToIndexedArray public static IndexedArrayItem[] ToIndexedArray(this IEnumerable source) @@ -192,14 +192,19 @@ namespace Umbraco.Web #region MediaUrl /// - /// Gets the url for the media. + /// Gets the url for a media. /// - /// The content. - /// The property alias to resolve the url from. - /// The variation language. - /// The url for the content. - /// Better use the GetMediaUrl method but that method is here to complement MediaUrlAbsolute(). - public static string MediaUrl(this IPublishedContent content, string propertyAlias, string culture = null) + /// The content item. + /// The culture (use current culture by default). + /// The url mode (use site configuration by default). + /// The alias of the property (use 'umbracoFile' by default). + /// The url for the media. + /// + /// The value of this property is contextual. It depends on the 'current' request uri, + /// if any. In addition, when the content type is multi-lingual, this is the url for the + /// specified culture. Otherwise, it is the invariant url. + /// + public static string MediaUrl(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Default, string propertyAlias = Constants.Conventions.Media.File) { var umbracoContext = Composing.Current.UmbracoContext; @@ -208,27 +213,7 @@ namespace Umbraco.Web if (umbracoContext.UrlProvider == null) throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null."); - return umbracoContext.UrlProvider.GetMediaUrl(content, propertyAlias, culture); - } - - /// - /// Gets the absolute url for the media. - /// - /// The content. - /// The property alias to resolve the url from. - /// The url mode. - /// The variation language. - /// The absolute url for the media. - public static string MediaUrl(this IPublishedContent content, string propertyAlias, UrlMode mode, string culture = null) - { - var umbracoContext = Composing.Current.UmbracoContext; - - if (umbracoContext == null) - throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null."); - if (umbracoContext.UrlProvider == null) - throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null."); - - return umbracoContext.UrlProvider.GetMediaUrl(content, propertyAlias, mode, culture); + return umbracoContext.UrlProvider.GetMediaUrl(content, mode, culture, propertyAlias); } #endregion diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index 0f79d11364..59e39fa80a 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -3,11 +3,8 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Services; using Umbraco.Web.Composing; -using Umbraco.Web.Models; namespace Umbraco.Web.Routing { @@ -83,11 +80,12 @@ namespace Umbraco.Web.Routing /// Gets the url of a published content. /// /// The published content identifier. + /// The url mode. /// A culture. /// The current absolute url. /// The url for the published content. - public string GetUrl(Guid id, string culture = null, Uri current = null) - => GetUrl(GetDocument(id), Mode, culture, current); + public string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string culture = null, Uri current = null) + => GetUrl(GetDocument(id), mode, culture, current); /// /// Gets the url of a published content. @@ -97,40 +95,9 @@ namespace Umbraco.Web.Routing /// A culture. /// The current absolute url. /// The url for the published content. - public string GetUrl(Guid id, UrlMode mode, string culture = null, Uri current = null) + public string GetUrl(int id, UrlMode mode = UrlMode.Default, string culture = null, Uri current = null) => GetUrl(GetDocument(id), mode, culture, current); - /// - /// Gets the url of a published content. - /// - /// The published content identifier. - /// A culture. - /// The current absolute url. - /// The url for the published content. - public string GetUrl(int id, string culture = null, Uri current = null) - => GetUrl(GetDocument(id), Mode, culture, current); - - /// - /// Gets the url of a published content. - /// - /// The published content identifier. - /// The url mode. - /// A culture. - /// The current absolute url. - /// The url for the published content. - public string GetUrl(int id, UrlMode mode, string culture = null, Uri current = null) - => GetUrl(GetDocument(id), mode, culture, current); - - /// - /// Gets the url of a published content. - /// - /// The published content. - /// A culture. - /// The current absolute url. - /// The url for the published content. - public string GetUrl(IPublishedContent content, string culture = null, Uri current = null) - => GetUrl(content, Mode, culture, current); - /// /// Gets the url of a published content. /// @@ -145,11 +112,14 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it returns "#". /// - public string GetUrl(IPublishedContent content, UrlMode mode, string culture = null, Uri current = null) + public string GetUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string culture = null, Uri current = null) { if (content == null || content.ContentType.ItemType == PublishedItemType.Element) return "#"; + if (mode == UrlMode.Default) + mode = Mode; + // this the ONLY place where we deal with default culture - IUrlProvider always receive a culture // be nice with tests, assume things can be null, ultimately fall back to invariant // (but only for variant content of course) @@ -214,23 +184,6 @@ namespace Umbraco.Web.Routing #region GetMediaUrl - /// - /// Gets the url of a media item. - /// - /// The published content. - /// The property alias to resolve the url from. - /// The variation language. - /// The current absolute url. - /// The url for the media. - /// - /// The url is absolute or relative depending on mode and on current. - /// If the media is multi-lingual, gets the url for the specified culture or, - /// when no culture is specified, the current culture. - /// If the provider is unable to provide a url, it returns . - /// - public string GetMediaUrl(IPublishedContent content, string propertyAlias, string culture = null, Uri current = null) - => GetMediaUrl(content, propertyAlias, Mode, culture, current); - /// /// Gets the url of a media item. /// @@ -246,13 +199,16 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it returns . /// - public string GetMediaUrl(IPublishedContent content, string propertyAlias, UrlMode mode, string culture = null, Uri current = null) + public string GetMediaUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri current = null) { if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); if (content == null) return ""; + if (mode == UrlMode.Default) + mode = Mode; + // this the ONLY place where we deal with default culture - IMediaUrlProvider always receive a culture // be nice with tests, assume things can be null, ultimately fall back to invariant // (but only for variant content of course) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 3947adc0e5..664818bda6 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -198,7 +198,7 @@ namespace Umbraco.Web /// The url for the content. public string Url(int contentId, string culture = null) { - return UrlProvider.GetUrl(contentId, culture); + return UrlProvider.GetUrl(contentId, culture: culture); } /// @@ -209,7 +209,7 @@ namespace Umbraco.Web /// The url for the content. public string Url(Guid contentId, string culture = null) { - return UrlProvider.GetUrl(contentId, culture); + return UrlProvider.GetUrl(contentId, culture: culture); } /// From bb0331e9ccac7b7f6b6667d85f04d3576db2603c Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 24 Apr 2019 14:25:41 +0200 Subject: [PATCH 022/218] Move IPublishedContent.Url() to ext method --- .../PublishedContent/IPublishedContent.cs | 14 ------- .../PublishedContentWrapped.cs | 3 -- .../PublishedContentCacheTests.cs | 4 +- .../PublishedMediaCacheTests.cs | 6 +-- .../DictionaryPublishedContent.cs | 5 +-- .../PublishedContentCache.cs | 9 +---- .../PublishedMediaCache.cs | 3 +- .../PublishedMemberCache.cs | 16 +++----- .../PublishedSnapshotService.cs | 4 +- .../XmlPublishedContent.cs | 14 +++---- .../Published/NestedContentTests.cs | 7 ++-- .../PublishedContent/NuCacheTests.cs | 3 -- .../Routing/MediaUrlProviderTests.cs | 10 ++--- src/Umbraco.Tests/Routing/UrlProviderTests.cs | 6 +-- .../Scoping/ScopedNuCacheTests.cs | 3 +- .../ContentTypeServiceVariantsTests.cs | 5 +-- .../Mapping/RedirectUrlMapDefinition.cs | 2 +- .../Models/PublishedContentBase.cs | 33 ---------------- .../PublishedCache/NuCache/ContentNode.cs | 12 +++--- .../PublishedCache/NuCache/ContentNodeKit.cs | 5 +-- .../PublishedCache/NuCache/ContentStore.cs | 5 +-- .../PublishedCache/NuCache/MemberCache.cs | 13 +++---- .../NuCache/PublishedContent.cs | 14 ++----- .../PublishedCache/NuCache/PublishedMember.cs | 11 ++---- .../NuCache/PublishedSnapshotService.cs | 19 ++++------ .../PublishedCache/PublishedMember.cs | 4 +- src/Umbraco.Web/PublishedContentExtensions.cs | 38 +++++++++++++++++++ src/Umbraco.Web/PublishedElementExtensions.cs | 4 +- .../Routing/UrlProviderExtensions.cs | 2 +- 29 files changed, 105 insertions(+), 169 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 6917538331..93487b6492 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -93,20 +93,6 @@ namespace Umbraco.Core.Models.PublishedContent /// DateTime UpdateDate { get; } - /// - /// Gets the url of the content item. - /// - /// - /// If the content item is a document, then this method returns the url of the - /// document. If it is a media, then this methods return the media url for the - /// 'umbracoFile' property. Use the MediaUrl() method to get the media url for other - /// properties. - /// The value of this property is contextual. It depends on the 'current' request uri, - /// if any. In addition, when the content type is multi-lingual, this is the url for the - /// specified culture. Otherwise, it is the invariant url. - /// - string Url(string culture = null, UrlMode mode = UrlMode.Auto); - /// /// Gets the culture date of the content item. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index b2feec38c6..3da690a7e2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -93,9 +93,6 @@ namespace Umbraco.Core.Models.PublishedContent /// public virtual DateTime UpdateDate => _content.UpdateDate; - /// - public virtual string Url(string culture = null, UrlMode mode = UrlMode.Auto) => _content.Url(culture, mode); - /// public DateTime CultureDate(string culture = null) => _content.CultureDate(culture); diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs index bb46b369c5..be160a483c 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs @@ -68,9 +68,9 @@ namespace Umbraco.Tests.Cache.PublishedCache var appCache = new DictionaryAppCache(); var domainCache = new DomainCache(ServiceContext.DomainService, DefaultCultureAccessor); var publishedShapshot = new PublishedSnapshot( - new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, new SiteDomainHelper(), umbracoContextAccessor, ContentTypesCache, null, null), + new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, ContentTypesCache, null, null), new PublishedMediaCache(xmlStore, ServiceContext.MediaService, ServiceContext.UserService, appCache, ContentTypesCache, Factory.GetInstance(), umbracoContextAccessor), - new PublishedMemberCache(null, appCache, Current.Services.MemberService, ContentTypesCache, umbracoContextAccessor), + new PublishedMemberCache(null, appCache, Current.Services.MemberService, ContentTypesCache), domainCache); var publishedSnapshotService = new Mock(); publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedShapshot); diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index bcd34ef2e3..d31401c725 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -323,8 +323,7 @@ namespace Umbraco.Tests.Cache.PublishedCache // no xpath null, // not from examine - false, - _umbracoContextAccessor), + false), //callback to get the children (dd, n) => children, // callback to get a property @@ -334,8 +333,7 @@ namespace Umbraco.Tests.Cache.PublishedCache // no xpath null, // not from examine - false, - _umbracoContextAccessor); + false); return dicDoc; } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index 811851224c..3a39d23ccb 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -8,7 +8,6 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; @@ -40,9 +39,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache IAppCache appCache, PublishedContentTypeCache contentTypeCache, XPathNavigator nav, - bool fromExamine, - IUmbracoContextAccessor umbracoContextAccessor) - :base(umbracoContextAccessor) + bool fromExamine) { if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary)); if (getParent == null) throw new ArgumentNullException(nameof(getParent)); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 8b6ac372ec..33348d071a 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -9,7 +9,6 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; -using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -19,7 +18,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache { private readonly IAppCache _appCache; private readonly IGlobalSettings _globalSettings; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly RoutesCache _routesCache; private readonly IDomainCache _domainCache; private readonly PublishedContentTypeCache _contentTypeCache; @@ -34,8 +32,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache IDomainCache domainCache, // an IDomainCache implementation IAppCache appCache, // an IAppCache that should be at request-level IGlobalSettings globalSettings, - ISiteDomainHelper siteDomainHelper, - IUmbracoContextAccessor umbracoContextAccessor, PublishedContentTypeCache contentTypeCache, // a PublishedContentType cache RoutesCache routesCache, // a RoutesCache string previewToken) // a preview token string (or null if not previewing) @@ -43,7 +39,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache { _appCache = appCache; _globalSettings = globalSettings; - _umbracoContextAccessor = umbracoContextAccessor; _routesCache = routesCache; // may be null for unit-testing _contentTypeCache = contentTypeCache; _domainCache = domainCache; @@ -318,13 +313,13 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing) { - return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache,_umbracoContextAccessor); + return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache); } private IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing) { return xmlNodes.Cast() - .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor)); + .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache)); } #endregion diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs index 7ebb026a21..ba43921f1c 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs @@ -674,8 +674,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _appCache, _contentTypeCache, cacheValues.XPath, // though, outside of tests, that should be null - cacheValues.FromExamine, - _umbracoContextAccessor + cacheValues.FromExamine ); return content.CreateModel(); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs index c28575f83d..19328c241e 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMemberCache.cs @@ -6,7 +6,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Security; @@ -18,16 +17,13 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly IAppCache _requestCache; private readonly XmlStore _xmlStore; private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public PublishedMemberCache(XmlStore xmlStore, IAppCache requestCache, IMemberService memberService, - PublishedContentTypeCache contentTypeCache, IUmbracoContextAccessor umbracoContextAccessor) + public PublishedMemberCache(XmlStore xmlStore, IAppCache requestCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache) { _requestCache = requestCache; _memberService = memberService; _xmlStore = xmlStore; _contentTypeCache = contentTypeCache; - _umbracoContextAccessor = umbracoContextAccessor; } public IPublishedContent GetByProviderKey(object key) @@ -44,7 +40,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var result = _memberService.GetByProviderKey(key); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(result, type).CreateModel(); }); } @@ -62,7 +58,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var result = _memberService.GetById(memberId); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(result, type).CreateModel(); }); } @@ -80,7 +76,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var result = _memberService.GetByUsername(username); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(result, type).CreateModel(); }); } @@ -98,14 +94,14 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var result = _memberService.GetByEmail(email); if (result == null) return null; var type = _contentTypeCache.Get(PublishedItemType.Member, result.ContentTypeId); - return new PublishedMember(result, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(result, type).CreateModel(); }); } public IPublishedContent GetByMember(IMember member) { var type = _contentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId); - return new PublishedMember(member, type, _umbracoContextAccessor).CreateModel(); + return new PublishedMember(member, type).CreateModel(); } public XPathNavigator CreateNavigator() diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs index 4a201ae44c..394a33d777 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedSnapshotService.cs @@ -145,9 +145,9 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var domainCache = new DomainCache(_domainService, _defaultCultureAccessor); return new PublishedSnapshot( - new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _siteDomainHelper,_umbracoContextAccessor, _contentTypeCache, _routesCache, previewToken), + new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache, previewToken), new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache, _entitySerializer, _umbracoContextAccessor), - new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache, _umbracoContextAccessor), + new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache), domainCache); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index 7d24eec1e9..2edb2bae32 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -26,23 +26,19 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache XmlNode xmlNode, bool isPreviewing, IAppCache appCache, - PublishedContentTypeCache contentTypeCache, - IUmbracoContextAccessor umbracoContextAccessor) - :base(umbracoContextAccessor) + PublishedContentTypeCache contentTypeCache) { _xmlNode = xmlNode; _isPreviewing = isPreviewing; _appCache = appCache; _contentTypeCache = contentTypeCache; - _umbracoContextAccessor = umbracoContextAccessor; } private readonly XmlNode _xmlNode; private readonly bool _isPreviewing; private readonly IAppCache _appCache; // at snapshot/request level (see PublishedContentCache) private readonly PublishedContentTypeCache _contentTypeCache; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly object _initializeLock = new object(); @@ -256,7 +252,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache if (parent == null) return; if (parent.Attributes?.GetNamedItem("isDoc") != null) - _parent = Get(parent, _isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor); + _parent = Get(parent, _isPreviewing, _appCache, _contentTypeCache); _parentInitialized = true; } @@ -413,7 +409,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var iterator = nav.Select(expr); _children = iterator.Cast() - .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _appCache, _contentTypeCache, _umbracoContextAccessor)) + .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing, _appCache, _contentTypeCache)) .OrderBy(x => x.SortOrder) .ToList(); @@ -433,7 +429,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache /// sure that we create only one instance of each for the duration of a request. The /// returned IPublishedContent is a model, if models are enabled. public static IPublishedContent Get(XmlNode node, bool isPreviewing, IAppCache appCache, - PublishedContentTypeCache contentTypeCache, IUmbracoContextAccessor umbracoContextAccessor) + PublishedContentTypeCache contentTypeCache) { // only 1 per request @@ -441,7 +437,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var id = attrs?.GetNamedItem("id").Value; if (id.IsNullOrWhiteSpace()) throw new InvalidOperationException("Node has no ID attribute."); var key = CacheKeyPrefix + id; // dont bother with preview, wont change during request in Xml cache - return (IPublishedContent) appCache.Get(key, () => (new XmlPublishedContent(node, isPreviewing, appCache, contentTypeCache, umbracoContextAccessor)).CreateModel()); + return (IPublishedContent) appCache.Get(key, () => (new XmlPublishedContent(node, isPreviewing, appCache, contentTypeCache)).CreateModel()); } public static void ClearRequest() diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 5e024c2a72..a1b736ae90 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -172,7 +172,7 @@ namespace Umbraco.Tests.Published new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }} ]") - }, Mock.Of()); + }); var value = content.Value("property1"); // nested single converter returns proper TestModel value @@ -200,8 +200,7 @@ namespace Umbraco.Tests.Published {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }}, {{ ""key"": ""{keyB}"", ""propertyN1"": ""bar"", ""ncContentTypeAlias"": ""contentN1"" }} ]") - }, - Mock.Of()); + }); var value = content.Value("property2"); // nested many converter returns proper IEnumerable value @@ -261,7 +260,7 @@ namespace Umbraco.Tests.Published class TestPublishedContent : PublishedContentBase { - public TestPublishedContent(IPublishedContentType contentType, Guid key, IEnumerable properties, IUmbracoContextAccessor umbracoContextAccessor): base(umbracoContextAccessor) + public TestPublishedContent(IPublishedContentType contentType, Guid key, IEnumerable properties) { ContentType = contentType; Key = key; diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 7d5fb8e736..dc035c1645 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -24,7 +24,6 @@ using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Umbraco.Web.Routing; namespace Umbraco.Tests.PublishedContent { @@ -171,7 +170,6 @@ namespace Umbraco.Tests.PublishedContent null, new TestPublishedSnapshotAccessor(), _variationAccesor, - Mock.Of(), Mock.Of(), scopeProvider, Mock.Of(), @@ -180,7 +178,6 @@ namespace Umbraco.Tests.PublishedContent new TestDefaultCultureAccessor(), dataSource, globalSettings, - new SiteDomainHelper(), Mock.Of(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 268e4e9d85..9eee6eb32d 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlMode.Auto, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto); Assert.AreEqual(expected, resolvedUrl); } @@ -64,7 +64,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlMode.Auto, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto); Assert.AreEqual(expected, resolvedUrl); } @@ -78,7 +78,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, mediaUrl, null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlMode.Absolute, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Absolute); Assert.AreEqual(expected, resolvedUrl); } @@ -89,7 +89,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.Boolean, "0", null); - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "test", UrlMode.Absolute, null, null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Absolute, propertyAlias: "test"); Assert.AreEqual(string.Empty, resolvedUrl); } @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Routing var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; - var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlMode.Auto, "da", null); + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto, "da"); Assert.AreEqual(daMediaUrl, resolvedUrl); } diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 3f39e4d4e8..ca13e06f0a 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -185,7 +185,7 @@ namespace Umbraco.Tests.Routing snapshotService: snapshotService.Object); //even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative url. - var url = umbracoContext.UrlProvider.GetUrl(1234, "fr-FR"); + var url = umbracoContext.UrlProvider.GetUrl(1234, culture: "fr-FR"); Assert.AreEqual("/home/test-fr/", url); } @@ -239,7 +239,7 @@ namespace Umbraco.Tests.Routing snapshotService: snapshotService.Object); - var url = umbracoContext.UrlProvider.GetUrl(1234, "fr-FR"); + var url = umbracoContext.UrlProvider.GetUrl(1234, culture: "fr-FR"); Assert.AreEqual("/home/test-fr/", url); } @@ -293,7 +293,7 @@ namespace Umbraco.Tests.Routing snapshotService: snapshotService.Object); - var url = umbracoContext.UrlProvider.GetUrl(1234, "fr-FR"); + var url = umbracoContext.UrlProvider.GetUrl(1234, culture: "fr-FR"); //the current uri is not the culture specific domain we want, so the result is an absolute path to the culture specific domain Assert.AreEqual("http://example.fr/home/test-fr/", url); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 3709490697..397a22fc62 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -92,13 +92,12 @@ namespace Umbraco.Tests.Scoping null, publishedSnapshotAccessor, Mock.Of(), - Mock.Of(), Logger, ScopeProvider, documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, new DatabaseDataSource(), - Factory.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index c70b96a175..3121988bfe 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -18,11 +18,9 @@ using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Tests.Testing; -using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Umbraco.Web.Routing; namespace Umbraco.Tests.Services { @@ -65,13 +63,12 @@ namespace Umbraco.Tests.Services null, publishedSnapshotAccessor, Mock.Of(), - Mock.Of(), Logger, ScopeProvider, documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, new DatabaseDataSource(), - Factory.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); diff --git a/src/Umbraco.Web/Models/Mapping/RedirectUrlMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/RedirectUrlMapDefinition.cs index 73123a0407..e773fcfee5 100644 --- a/src/Umbraco.Web/Models/Mapping/RedirectUrlMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/RedirectUrlMapDefinition.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Models.Mapping target.ContentId = source.ContentId; target.CreateDateUtc = source.CreateDateUtc; target.Culture = source.Culture; - target.DestinationUrl = source.ContentId > 0 ? UmbracoContext?.UrlProvider?.GetUrl(source.ContentId, source.Culture) : "#"; + target.DestinationUrl = source.ContentId > 0 ? UmbracoContext?.UrlProvider?.GetUrl(source.ContentId, culture: source.Culture) : "#"; target.OriginalUrl = UmbracoContext?.UrlProvider?.GetUrlFromRoute(source.ContentId, source.Url, source.Culture); target.RedirectId = source.Key; } diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 25c84c97e5..935c594285 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -15,13 +15,6 @@ namespace Umbraco.Web.Models [DebuggerDisplay("Content Id: {Id}}")] public abstract class PublishedContentBase : IPublishedContent { - protected PublishedContentBase(IUmbracoContextAccessor umbracoContextAccessor) - { - UmbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - } - - protected IUmbracoContextAccessor UmbracoContextAccessor { get; } - #region ContentType public abstract IPublishedContentType ContentType { get; } @@ -76,32 +69,6 @@ namespace Umbraco.Web.Models /// public abstract DateTime UpdateDate { get; } - /// - /// - /// The url of documents are computed by the document url providers. The url of medias are computed by the media url providers. - /// - public virtual string Url(string culture = null, UrlMode mode = UrlMode.Auto) - { - var umbracoContext = UmbracoContextAccessor.UmbracoContext; - - if (umbracoContext == null) - throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext is null."); - if (umbracoContext.UrlProvider == null) - throw new InvalidOperationException("Cannot compute Url for a content item when UmbracoContext.UrlProvider is null."); - - switch (ContentType.ItemType) - { - case PublishedItemType.Content: - return umbracoContext.UrlProvider.GetUrl(this, mode, culture); - - case PublishedItemType.Media: - return umbracoContext.UrlProvider.GetMediaUrl(this, mode, culture, Constants.Conventions.Media.File); - - default: - throw new NotSupportedException(); - } - } - /// public abstract DateTime CultureDate(string culture = null); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index 7c9a739448..1e8d5ddfc9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache.NuCache.DataSource; @@ -40,11 +39,10 @@ namespace Umbraco.Web.PublishedCache.NuCache DateTime createDate, int creatorId, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, - IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor) + IVariationContextAccessor variationContextAccessor) : this(id, uid, level, path, sortOrder, parentContentId, createDate, creatorId) { - SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); + SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor); } // 2-phases ctor, phase 1 @@ -66,7 +64,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // two-phase ctor, phase 2 - public void SetContentTypeAndData(IPublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) + public void SetContentTypeAndData(IPublishedContentType contentType, ContentData draftData, ContentData publishedData, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) { ContentType = contentType; @@ -75,13 +73,13 @@ namespace Umbraco.Web.PublishedCache.NuCache if (draftData != null) { - DraftContent = new PublishedContent(this, draftData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); + DraftContent = new PublishedContent(this, draftData, publishedSnapshotAccessor, variationContextAccessor); DraftModel = DraftContent.CreateModel(); } if (publishedData != null) { - PublishedContent = new PublishedContent(this, publishedData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); + PublishedContent = new PublishedContent(this, publishedData, publishedSnapshotAccessor, variationContextAccessor); PublishedModel = PublishedContent.CreateModel(); } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs index 08557fe3db..5d757fff7b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs @@ -21,8 +21,7 @@ namespace Umbraco.Web.PublishedCache.NuCache IPublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, - bool canBePublished, - IUmbracoContextAccessor umbracoContextAccessor) + bool canBePublished) { var draftData = DraftData; @@ -35,7 +34,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (draftData == null && !canBePublished) draftData = PublishedData; - Node.SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor,umbracoContextAccessor); + Node.SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor); } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 9b1955fe43..298b98ca05 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -21,7 +21,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IVariationContextAccessor _variationContextAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ConcurrentDictionary> _contentNodes; private LinkedNode _root; private readonly ConcurrentDictionary> _contentTypesById; @@ -49,13 +48,11 @@ namespace Umbraco.Web.PublishedCache.NuCache public ContentStore( IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, BPlusTree localDb = null) { _publishedSnapshotAccessor = publishedSnapshotAccessor; _variationContextAccessor = variationContextAccessor; - _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; _localDb = localDb; @@ -449,7 +446,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var canBePublished = ParentPublishedLocked(kit); // and use - kit.Build(link.Value, _publishedSnapshotAccessor, _variationContextAccessor, canBePublished, _umbracoContextAccessor); + kit.Build(link.Value, _publishedSnapshotAccessor, _variationContextAccessor, canBePublished); return true; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs index 5164b2b3bf..2e196f629e 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs @@ -8,7 +8,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; using Umbraco.Core.Xml.XPath; using Umbraco.Web.PublishedCache.NuCache.Navigable; @@ -19,7 +18,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; public readonly IVariationContextAccessor VariationContextAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IEntityXmlSerializer _entitySerializer; private readonly IAppCache _snapshotCache; private readonly IMemberService _memberService; @@ -27,12 +25,11 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly bool _previewDefault; public MemberCache(bool previewDefault, IAppCache snapshotCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor, IEntityXmlSerializer entitySerializer) + IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IEntityXmlSerializer entitySerializer) { _snapshotCache = snapshotCache; _publishedSnapshotAccessor = publishedSnapshotAccessor; VariationContextAccessor = variationContextAccessor; - _umbracoContextAccessor = umbracoContextAccessor; _entitySerializer = entitySerializer; _memberService = memberService; _previewDefault = previewDefault; @@ -68,14 +65,14 @@ namespace Umbraco.Web.PublishedCache.NuCache var member = _memberService.GetById(memberId); return member == null ? null - : PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor); + : PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor); }); } private IPublishedContent /*IPublishedMember*/ GetById(IMember member, bool previewing) { return GetCacheItem(CacheKeys.MemberCacheMember("ById", _previewDefault, member.Id), () => - PublishedMember.Create(member, GetContentType(member.ContentTypeId), previewing, _publishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor)); + PublishedMember.Create(member, GetContentType(member.ContentTypeId), previewing, _publishedSnapshotAccessor, VariationContextAccessor)); } public IPublishedContent /*IPublishedMember*/ GetByProviderKey(object key) @@ -110,7 +107,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public IPublishedContent /*IPublishedMember*/ GetByMember(IMember member) { - return PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor); + return PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, VariationContextAccessor); } public IEnumerable GetAtRoot(bool preview) @@ -118,7 +115,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // because members are flat (not a tree) everything is at root // because we're loading everything... let's just not cache? var members = _memberService.GetAllMembers(); - return members.Select(m => PublishedMember.Create(m, GetContentType(m.ContentTypeId), preview, _publishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor)); + return members.Select(m => PublishedMember.Create(m, GetContentType(m.ContentTypeId), preview, _publishedSnapshotAccessor, VariationContextAccessor)); } public XPathNavigator CreateNavigator() diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 0a55049f9f..c227c75952 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -22,9 +22,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor, - IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor) - : base(umbracoContextAccessor) + IVariationContextAccessor variationContextAccessor) { _contentNode = contentNode ?? throw new ArgumentNullException(nameof(contentNode)); ContentData = contentData ?? throw new ArgumentNullException(nameof(contentData)); @@ -69,7 +67,6 @@ namespace Umbraco.Web.PublishedCache.NuCache // used when cloning in ContentNode public PublishedContent(ContentNode contentNode, PublishedContent origin) - : base(origin.UmbracoContextAccessor) { _contentNode = contentNode; _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; @@ -86,10 +83,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // clone for previewing as draft a published content that is published and has no draft - private PublishedContent( - PublishedContent origin, - IUmbracoContextAccessor umbracoContextAccessor) - : base(umbracoContextAccessor) + private PublishedContent(PublishedContent origin) { _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; VariationContextAccessor = origin.VariationContextAccessor; @@ -406,8 +400,8 @@ namespace Umbraco.Web.PublishedCache.NuCache return this; var cache = GetAppropriateCache(); - if (cache == null) return new PublishedContent(this, UmbracoContextAccessor).CreateModel(); - return (IPublishedContent)cache.Get(AsPreviewingCacheKey, () => new PublishedContent(this, UmbracoContextAccessor).CreateModel()); + if (cache == null) return new PublishedContent(this).CreateModel(); + return (IPublishedContent)cache.Get(AsPreviewingCacheKey, () => new PublishedContent(this).CreateModel()); } // used by Navigable.Source,... diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs index 4bfcbb2a3d..dc608fe391 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedMember.cs @@ -20,10 +20,8 @@ namespace Umbraco.Web.PublishedCache.NuCache ContentNode contentNode, ContentData contentData, IPublishedSnapshotAccessor publishedSnapshotAccessor, - IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor - ) - : base(contentNode, contentData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor) + IVariationContextAccessor variationContextAccessor) + : base(contentNode, contentData, publishedSnapshotAccessor, variationContextAccessor) { _member = member; } @@ -33,8 +31,7 @@ namespace Umbraco.Web.PublishedCache.NuCache IPublishedContentType contentType, bool previewing, IPublishedSnapshotAccessor publishedSnapshotAccessor, - IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor) + IVariationContextAccessor variationContextAccessor) { var d = new ContentData { @@ -50,7 +47,7 @@ namespace Umbraco.Web.PublishedCache.NuCache member.Level, member.Path, member.SortOrder, member.ParentId, member.CreateDate, member.CreatorId); - return new PublishedMember(member, n, d, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor).CreateModel(); + return new PublishedMember(member, n, d, publishedSnapshotAccessor, variationContextAccessor).CreateModel(); } private static Dictionary GetPropertyValues(IPublishedContentType contentType, IMember member) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index f847a9f2ce..5a3672ed57 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -34,7 +34,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { private readonly ServiceContext _serviceContext; private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IScopeProvider _scopeProvider; private readonly IDataSource _dataSource; private readonly ILogger _logger; @@ -42,7 +41,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IMediaRepository _mediaRepository; private readonly IMemberRepository _memberRepository; private readonly IGlobalSettings _globalSettings; - private readonly ISiteDomainHelper _siteDomainHelper; private readonly IEntityXmlSerializer _entitySerializer; private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly UrlSegmentProviderCollection _urlSegmentProviders; @@ -83,11 +81,10 @@ namespace Umbraco.Web.PublishedCache.NuCache public PublishedSnapshotService(Options options, IMainDom mainDom, IRuntimeState runtime, ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap, - IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor, ILogger logger, IScopeProvider scopeProvider, + IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, ILogger logger, IScopeProvider scopeProvider, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IDefaultCultureAccessor defaultCultureAccessor, - IDataSource dataSource, IGlobalSettings globalSettings, ISiteDomainHelper siteDomainHelper, + IDataSource dataSource, IGlobalSettings globalSettings, IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory, UrlSegmentProviderCollection urlSegmentProviders) : base(publishedSnapshotAccessor, variationContextAccessor) @@ -97,7 +94,6 @@ namespace Umbraco.Web.PublishedCache.NuCache _serviceContext = serviceContext; _publishedContentTypeFactory = publishedContentTypeFactory; - _umbracoContextAccessor = umbracoContextAccessor; _dataSource = dataSource; _logger = logger; _scopeProvider = scopeProvider; @@ -106,7 +102,6 @@ namespace Umbraco.Web.PublishedCache.NuCache _memberRepository = memberRepository; _defaultCultureAccessor = defaultCultureAccessor; _globalSettings = globalSettings; - _siteDomainHelper = siteDomainHelper; _urlSegmentProviders = urlSegmentProviders; // we need an Xml serializer here so that the member cache can support XPath, @@ -156,13 +151,13 @@ namespace Umbraco.Web.PublishedCache.NuCache // stores are created with a db so they can write to it, but they do not read from it, // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to // figure out whether it can read the databases or it should populate them from sql - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _umbracoContextAccessor, logger, _localContentDb); - _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _umbracoContextAccessor, logger, _localMediaDb); + _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localContentDb); + _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localMediaDb); } else { - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _umbracoContextAccessor, logger); - _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _umbracoContextAccessor, logger); + _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger); + _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger); } _domainStore = new SnapDictionary(); @@ -1077,7 +1072,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainCache, _globalSettings), MediaCache = new MediaCache(previewDefault, mediaSnap, snapshotCache, elementsCache), - MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor, _umbracoContextAccessor, _entitySerializer), + MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor, _entitySerializer), DomainCache = domainCache, SnapshotCache = snapshotCache, ElementsCache = elementsCache diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 08e00c1f17..9c6f60da97 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -21,9 +21,7 @@ namespace Umbraco.Web.PublishedCache public PublishedMember( IMember member, - IPublishedContentType publishedMemberType, - IUmbracoContextAccessor umbracoContextAccessor) - :base(umbracoContextAccessor) + IPublishedContentType publishedMemberType) { _member = member ?? throw new ArgumentNullException(nameof(member)); _membershipUser = member; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index b31527eb57..eb38826193 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1208,5 +1208,43 @@ namespace Umbraco.Web } #endregion + + #region Url + + /// + /// Gets the url of the content item. + /// + /// + /// If the content item is a document, then this method returns the url of the + /// document. If it is a media, then this methods return the media url for the + /// 'umbracoFile' property. Use the MediaUrl() method to get the media url for other + /// properties. + /// The value of this property is contextual. It depends on the 'current' request uri, + /// if any. In addition, when the content type is multi-lingual, this is the url for the + /// specified culture. Otherwise, it is the invariant url. + /// + public static string Url(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Auto) + { + var umbracoContext = Composing.Current.UmbracoContext; + + if (umbracoContext == null) + throw new InvalidOperationException("Cannot resolve a Url when Current.UmbracoContext is null."); + if (umbracoContext.UrlProvider == null) + throw new InvalidOperationException("Cannot resolve a Url when Current.UmbracoContext.UrlProvider is null."); + + switch (content.ContentType.ItemType) + { + case PublishedItemType.Content: + return umbracoContext.UrlProvider.GetUrl(content, mode, culture); + + case PublishedItemType.Media: + return umbracoContext.UrlProvider.GetMediaUrl(content, mode, culture, Constants.Conventions.Media.File); + + default: + throw new NotSupportedException(); + } + } + + #endregion } } diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs index 2de8259d3c..c35c85c606 100644 --- a/src/Umbraco.Web/PublishedElementExtensions.cs +++ b/src/Umbraco.Web/PublishedElementExtensions.cs @@ -209,9 +209,9 @@ namespace Umbraco.Web var umbracoContext = Composing.Current.UmbracoContext; if (umbracoContext == null) - throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext is null."); + throw new InvalidOperationException("Cannot resolve a Url when Current.UmbracoContext is null."); if (umbracoContext.UrlProvider == null) - throw new InvalidOperationException("Cannot resolve a Url for a content item when Current.UmbracoContext.UrlProvider is null."); + throw new InvalidOperationException("Cannot resolve a Url when Current.UmbracoContext.UrlProvider is null."); return umbracoContext.UrlProvider.GetMediaUrl(content, mode, culture, propertyAlias); } diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 4b66d56830..27a27399bf 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -109,7 +109,7 @@ namespace Umbraco.Web.Routing string url; try { - url = umbracoContext.UrlProvider.GetUrl(content.Id, culture); + url = umbracoContext.UrlProvider.GetUrl(content.Id, culture: culture); } catch (Exception ex) { From c4514989753ae2e401833c8fb7725ee59decca26 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 29 May 2019 15:13:28 +0200 Subject: [PATCH 023/218] Fix v8/dev merge --- src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs | 2 +- src/Umbraco.Web/Templates/TemplateUtilities.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 3a5405548b..c0a6e97dda 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -99,7 +99,7 @@ namespace Umbraco.Tests.Web var snapshotService = Mock.Of(); Mock.Get(snapshotService).Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(snapshot); var media = Mock.Of(); - Mock.Get(media).Setup(x => x.Url).Returns("/media/1001/my-image.jpg"); + Mock.Get(media).Setup(x => x.Url(It.IsAny(), It.IsAny())).Returns("/media/1001/my-image.jpg"); var mediaCache = Mock.Of(); Mock.Get(mediaCache).Setup(x => x.GetById(It.IsAny())).Returns(media); diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 040c4e128a..06706e475b 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.Templates if (guidUdi.EntityType == Constants.UdiEntityType.Document) newLink = urlProvider.GetUrl(guidUdi.Guid); else if (guidUdi.EntityType == Constants.UdiEntityType.Media) - newLink = mediaCache.GetById(guidUdi.Guid)?.Url; + newLink = mediaCache.GetById(guidUdi.Guid)?.Url(); if (newLink == null) newLink = "#"; From 983144c3f98d54b46723e66ce711dc94193c6e6c Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 29 May 2019 16:48:20 +0200 Subject: [PATCH 024/218] Unit-test published content linked-list children --- .../PublishedContent/NuCacheChildrenTests.cs | 522 ++++++++++++++++++ .../Testing/Objects/TestDataSource.cs | 26 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../PublishedCache/NuCache/ContentNodeKit.cs | 9 + .../NuCache/DataSource/IDataSource.cs | 10 +- 5 files changed, 554 insertions(+), 14 deletions(-) create mode 100644 src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs new file mode 100644 index 0000000000..d9cb8dbbc2 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -0,0 +1,522 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing.Objects; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web.Cache; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.PublishedCache.NuCache.DataSource; + +namespace Umbraco.Tests.PublishedContent +{ + [TestFixture] + public class NuCacheChildrenTests + { + private IPublishedSnapshotService _snapshotService; + private IVariationContextAccessor _variationAccesor; + private IPublishedSnapshotAccessor _snapshotAccessor; + private ContentType _contentType; + private PropertyType _propertyType; + private TestDataSource _source; + + private void Init(IEnumerable kits) + { + Current.Reset(); + Current.UnlockConfigs(); + Current.Configs.Add(SettingsForTests.GenerateMockUmbracoSettings); + Current.Configs.Add(() => new GlobalSettings()); + var globalSettings = Current.Configs.Global(); + + // create a data source for NuCache + _source = new TestDataSource(kits); + + var runtime = Mock.Of(); + Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run); + + // create data types, property types and content types + var dataType = new DataType(new VoidEditor("Editor", Mock.Of())) { Id = 3 }; + + var dataTypes = new[] + { + dataType + }; + + _propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Nothing }; + _contentType = new ContentType(-1) { Id = 2, Alias = "ctype", Variations = ContentVariation.Nothing }; + _contentType.AddPropertyType(_propertyType); + + var contentTypes = new[] + { + _contentType + }; + + var contentTypeService = Mock.Of(); + Mock.Get(contentTypeService).Setup(x => x.GetAll()).Returns(contentTypes); + Mock.Get(contentTypeService).Setup(x => x.GetAll(It.IsAny())).Returns(contentTypes); + + var contentTypeServiceBaseFactory = Mock.Of(); + Mock.Get(contentTypeServiceBaseFactory).Setup(x => x.For(It.IsAny())).Returns(contentTypeService); + + var dataTypeService = Mock.Of(); + Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes); + + // create a service context + var serviceContext = ServiceContext.CreatePartial( + dataTypeService: dataTypeService, + memberTypeService: Mock.Of(), + memberService: Mock.Of(), + contentTypeService: contentTypeService, + localizationService: Mock.Of() + ); + + // create a scope provider + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.CreateScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Mock.Of); + + // create a published content type factory + var contentTypeFactory = new PublishedContentTypeFactory( + Mock.Of(), + new PropertyValueConverterCollection(Array.Empty()), + dataTypeService); + + // create accessors + _variationAccesor = new TestVariationContextAccessor(); + _snapshotAccessor = new TestPublishedSnapshotAccessor(); + + // at last, create the complete NuCache snapshot service! + var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + _snapshotService = new PublishedSnapshotService(options, + null, + runtime, + serviceContext, + contentTypeFactory, + null, + _snapshotAccessor, + _variationAccesor, + Mock.Of(), + scopeProvider, + Mock.Of(), + Mock.Of(), + Mock.Of(), + new TestDefaultCultureAccessor(), + _source, + globalSettings, + Mock.Of(), + Mock.Of(), + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + + // invariant is the current default + _variationAccesor.VariationContext = new VariationContext(); + } + + private IEnumerable GetKits() + { + var paths = new Dictionary { { -1, "-1" } }; + + ContentNodeKit CreateKit(int id, int parentId, int sortOrder) + { + if (!paths.TryGetValue(parentId, out var parentPath)) + throw new Exception("Unknown parent."); + + var path = paths[id] = parentPath + "," + id; + var level = path.Count(x => x == ','); + + return new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = "N" + id, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + } + + yield return CreateKit(1, -1, 1); + yield return CreateKit(2, -1, 2); + yield return CreateKit(3, -1, 3); + + yield return CreateKit(4, 1, 1); + yield return CreateKit(5, 1, 2); + yield return CreateKit(6, 1, 3); + + yield return CreateKit(7, 2, 3); + yield return CreateKit(8, 2, 2); + yield return CreateKit(9, 2, 1); + + yield return CreateKit(10, 3, 1); + + yield return CreateKit(11, 4, 1); + yield return CreateKit(12, 4, 2); + } + + [Test] + public void EmptyTest() + { + Init(Enumerable.Empty()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var documents = snapshot.Content.GetAtRoot().ToArray(); + Assert.AreEqual(0, documents.Length); + } + + [Test] + public void ChildrenTest() + { + Init(GetKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8", "N7"); + + documents = snapshot.Content.GetById(3).Children().ToArray(); + AssertDocuments(documents, "N10"); + + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N11", "N12"); + + documents = snapshot.Content.GetById(10).Children().ToArray(); + AssertDocuments(documents); + } + + [Test] + public void ParentTest() + { + Init(GetKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + Assert.IsNull(snapshot.Content.GetById(1).Parent()); + Assert.IsNull(snapshot.Content.GetById(2).Parent()); + Assert.IsNull(snapshot.Content.GetById(3).Parent()); + + Assert.AreEqual(1, snapshot.Content.GetById(4).Parent()?.Id); + Assert.AreEqual(1, snapshot.Content.GetById(5).Parent()?.Id); + Assert.AreEqual(1, snapshot.Content.GetById(6).Parent()?.Id); + + Assert.AreEqual(2, snapshot.Content.GetById(7).Parent()?.Id); + Assert.AreEqual(2, snapshot.Content.GetById(8).Parent()?.Id); + Assert.AreEqual(2, snapshot.Content.GetById(9).Parent()?.Id); + + Assert.AreEqual(3, snapshot.Content.GetById(10).Parent()?.Id); + + Assert.AreEqual(4, snapshot.Content.GetById(11).Parent()?.Id); + Assert.AreEqual(4, snapshot.Content.GetById(12).Parent()?.Id); + } + + [Test] + public void MoveToRootTest() + { + Init(GetKits()); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + // do some changes + var kit = _source.Kits[10]; + _source.Kits[10] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), 1, "-1,10", 4, -1, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + // notify + _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(10, TreeChangeTypes.RefreshBranch) }, out _, out _); + + // changes that *I* make are immediately visible on the current snapshot + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3", "N10"); + + documents = snapshot.Content.GetById(3).Children().ToArray(); + AssertDocuments(documents); + + Assert.IsNull(snapshot.Content.GetById(10).Parent()); + } + + [Test] + public void MoveFromRootTest() + { + Init(GetKits()); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + // do some changes + var kit = _source.Kits[1]; + _source.Kits[1] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), 1, "-1,3,10,1", 1, 10, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + // notify + _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch) }, out _, out _); + + // changes that *I* make are immediately visible on the current snapshot + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N2", "N3"); + + documents = snapshot.Content.GetById(10).Children().ToArray(); + AssertDocuments(documents, "N1"); + + Assert.AreEqual(10, snapshot.Content.GetById(1).Parent()?.Id); + } + + [Test] + public void ReOrderTest() + { + Init(GetKits()); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + // do some changes + var kit = _source.Kits[7]; + _source.Kits[7] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 1, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[8]; + _source.Kits[8] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 3, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[9]; + _source.Kits[9] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 2, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + // notify + _snapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(kit.Node.ParentContentId, TreeChangeTypes.RefreshBranch) }, out _, out _); + + // changes that *I* make are immediately visible on the current snapshot + var documents = snapshot.Content.GetById(kit.Node.ParentContentId).Children().ToArray(); + AssertDocuments(documents, "N7", "N9", "N8"); + } + + [Test] + public void MoveTest() + { + Init(GetKits()); + + // get snapshot + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + // do some changes + var kit = _source.Kits[4]; + _source.Kits[4] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 2, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[5]; + _source.Kits[5] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 3, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[6]; + _source.Kits[6] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, kit.Node.Path, 4, kit.Node.ParentContentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + kit = _source.Kits[7]; + _source.Kits[7] = new ContentNodeKit + { + ContentTypeId = 2, + Node = new ContentNode(kit.Node.Id, Guid.NewGuid(), kit.Node.Level, "-1,1,7", 1, 1, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = kit.PublishedData.Name, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = DateTime.Now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; + + // notify + _snapshotService.Notify(new[] + { + // removal must come first + new ContentCacheRefresher.JsonPayload(2, TreeChangeTypes.RefreshBranch), + new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch) + }, out _, out _); + + // changes that *I* make are immediately visible on the current snapshot + var documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N7", "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8"); + + Assert.AreEqual(1, snapshot.Content.GetById(7).Parent()?.Id); + } + + private void AssertDocuments(IPublishedContent[] documents, params string[] names) + { + Assert.AreEqual(names.Length, documents.Length); + for (var i = 0; i < names.Length; i++) + Assert.AreEqual(names[i], documents[i].Name()); + } + } +} diff --git a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs index 26bfff0e1a..72fb89ab82 100644 --- a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs +++ b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs @@ -9,30 +9,38 @@ namespace Umbraco.Tests.Testing.Objects { internal class TestDataSource : IDataSource { - private readonly Dictionary _kits; - public TestDataSource(params ContentNodeKit[] kits) : this((IEnumerable) kits) { } public TestDataSource(IEnumerable kits) { - _kits = kits.ToDictionary(x => x.Node.Id, x => x); + Kits = kits.ToDictionary(x => x.Node.Id, x => x); } + public Dictionary Kits { get; } + + // note: it is important to clone the returned kits, as the inner + // ContentNode is directly reused and modified by the snapshot service + public ContentNodeKit GetContentSource(IScope scope, int id) - => _kits.TryGetValue(id, out var kit) ? kit : default; + => Kits.TryGetValue(id, out var kit) ? kit.Clone() : default; public IEnumerable GetAllContentSources(IScope scope) - => _kits.Values; + => Kits.Values + .OrderBy(x => x.Node.Level) + .Select(x => x.Clone()); public IEnumerable GetBranchContentSources(IScope scope, int id) - { - throw new NotImplementedException(); - } + => Kits.Values + .Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ",")) + .OrderBy(x => x.Node.Level).ThenBy(x => x.Node.SortOrder) + .Select(x => x.Clone()); public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) - => _kits.Values.Where(x => ids.Contains(x.ContentTypeId)); + => Kits.Values + .Where(x => ids.Contains(x.ContentTypeId)) + .Select(x => x.Clone()); public ContentNodeKit GetMediaSource(IScope scope, int id) { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ddd67df6e5..cd3155c7c3 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -139,6 +139,7 @@ + diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs index 5d757fff7b..61fb5c12a3 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNodeKit.cs @@ -36,5 +36,14 @@ namespace Umbraco.Web.PublishedCache.NuCache Node.SetContentTypeAndData(contentType, draftData, publishedData, publishedSnapshotAccessor, variationContextAccessor); } + + public ContentNodeKit Clone() + => new ContentNodeKit + { + ContentTypeId = ContentTypeId, + DraftData = DraftData, + PublishedData = PublishedData, + Node = new ContentNode(Node) + }; } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/IDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/IDataSource.cs index 323d954980..d4702cf7aa 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/IDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/IDataSource.cs @@ -9,13 +9,13 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource internal interface IDataSource { ContentNodeKit GetContentSource(IScope scope, int id); - IEnumerable GetAllContentSources(IScope scope); - IEnumerable GetBranchContentSources(IScope scope, int id); + IEnumerable GetAllContentSources(IScope scope); // must order by level + IEnumerable GetBranchContentSources(IScope scope, int id); // must order by level, sortOrder IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids); ContentNodeKit GetMediaSource(IScope scope, int id); - IEnumerable GetAllMediaSources(IScope scope); - IEnumerable GetBranchMediaSources(IScope scope, int id); + IEnumerable GetAllMediaSources(IScope scope); // must order by level + IEnumerable GetBranchMediaSources(IScope scope, int id); // must order by level, sortOrder IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids); } -} \ No newline at end of file +} From c17dbbe141f2485a1a6ac0a8686303554cba72f8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 6 Jun 2019 12:16:55 +0200 Subject: [PATCH 025/218] Fix merge --- .../ValueConverters/MultiNodeTreePickerValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index d402a0e714..47f8797295 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -153,7 +153,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return content; } - private static bool IsSingleNodePicker(PublishedPropertyType propertyType) + private static bool IsSingleNodePicker(IPublishedPropertyType propertyType) { return propertyType.DataType.ConfigurationAs().MaxNumber == 1; } From 7fd6bfa16337d26c4b184b9ede7694649ebabada Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 6 Jun 2019 16:54:00 +0200 Subject: [PATCH 026/218] Make IPublishedContent changes less breaking and more friendly --- .../PublishedContent/IPublishedContent.cs | 47 ++++--- .../PublishedContentWrapped.cs | 18 ++- .../PublishedContentExtensions.cs | 128 ++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../PublishedMediaCacheTests.cs | 22 +-- .../DictionaryPublishedContent.cs | 22 +-- .../PublishedContentCache.cs | 4 +- .../XmlPublishedContent.cs | 47 ++++--- .../Published/NestedContentTests.cs | 13 +- .../PublishedContent/NuCacheChildrenTests.cs | 30 ++-- .../PublishedContentDataTableTests.cs | 31 ++--- .../PublishedContentLanguageVariantTests.cs | 46 +++---- .../PublishedContentMoreTests.cs | 26 ++-- .../PublishedContent/PublishedMediaTests.cs | 6 +- .../PublishedContent/PublishedRouterTests.cs | 4 +- .../PublishedContent/RootNodeTests.cs | 2 +- .../SolidPublishedSnapshot.cs | 31 ++--- .../TestHelpers/Stubs/TestPublishedContent.cs | 42 ++---- .../Web/TemplateUtilitiesTests.cs | 2 +- src/Umbraco.Web.UI.Client/package-lock.json | 99 ++++++-------- .../ListChildPagesFromChangeableSource.cshtml | 2 +- .../ListChildPagesFromCurrentPage.cshtml | 2 +- .../ListChildPagesOrderedByDate.cshtml | 2 +- .../ListChildPagesOrderedByName.cshtml | 2 +- .../ListChildPagesOrderedByProperty.cshtml | 2 +- .../ListDescendantsFromCurrentPage.cshtml | 6 +- .../ListImagesFromMediaFolder.cshtml | 2 +- .../Templates/Navigation.cshtml | 2 +- .../Templates/SiteMap.cshtml | 2 +- .../Controllers/UmbLoginController.cs | 2 +- .../Editors/TemplateQueryController.cs | 10 +- .../PublishedContentHashtableConverter.cs | 56 ++++---- .../PublishedValueFallback.cs | 2 +- .../Models/PublishedContentBase.cs | 19 ++- .../PublishedCache/NuCache/ContentCache.cs | 4 +- .../NuCache/Navigable/NavigableContent.cs | 4 +- .../NuCache/PublishedContent.cs | 111 +++++---------- .../PublishedCache/PublishedMember.cs | 20 ++- src/Umbraco.Web/PublishedContentExtensions.cs | 48 ++----- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 2 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 2 +- src/Umbraco.Web/Routing/PublishedRouter.cs | 2 +- .../Routing/RedirectTrackingComponent.cs | 4 +- .../Routing/UrlProviderExtensions.cs | 2 +- .../Templates/TemplateUtilities.cs | 4 +- 45 files changed, 481 insertions(+), 454 deletions(-) create mode 100644 src/Umbraco.Core/PublishedContentExtensions.cs diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 93487b6492..1c0d39a8b8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; namespace Umbraco.Core.Models.PublishedContent { - /// /// /// Represents a published content item. @@ -27,16 +26,14 @@ namespace Umbraco.Core.Models.PublishedContent int Id { get; } /// - /// Gets the name of the content item. + /// Gets the name of the content item for the current culture. /// - /// The specific culture to get the name for. If null is used the current culture is used (Default is null). - string Name(string culture = null); + string Name { get; } /// - /// Gets the url segment of the content item. + /// Gets the url segment of the content item for the current culture. /// - /// The specific culture to get the url segment for. If null is used the current culture is used (Default is null). - string UrlSegment(string culture = null); + string UrlSegment { get; } /// /// Gets the sort order of the content item. @@ -94,20 +91,29 @@ namespace Umbraco.Core.Models.PublishedContent DateTime UpdateDate { get; } /// - /// Gets the culture date of the content item. + /// Gets the url of the content item for the current culture. /// - /// The specific culture to get the name for. If null is used the current culture is used (Default is null). - DateTime CultureDate(string culture = null); + /// + /// The value of this property is contextual. It depends on the 'current' request uri, + /// if any. + /// + string Url { get; } /// - /// Gets all available cultures. + /// Gets available culture infos. /// /// /// Contains only those culture that are available. For a published content, these are /// the cultures that are published. For a draft content, those that are 'available' ie /// have a non-empty content name. + /// Does not contain the invariant culture. // fixme? /// - IReadOnlyCollection Cultures { get; } + IReadOnlyDictionary Cultures { get; } + + /// + /// Gets the type of the content item (document, media...). + /// + PublishedItemType ItemType { get; } /// /// Gets a value indicating whether the content is draft. @@ -145,18 +151,17 @@ namespace Umbraco.Core.Models.PublishedContent /// Gets the parent of the content item. /// /// The parent of root content is null. - IPublishedContent Parent(); + IPublishedContent Parent { get; } /// - /// Gets the children of the content item. + /// Gets the children of the content item that are available for the current culture. /// - /// The specific culture to get the url children for. If null is used the current culture is used (Default is null). - /// - /// Gets children that are available for the specified culture. - /// Children are sorted by their sortOrder. - /// The '*' culture and supported and returns everything. - /// - IEnumerable Children(string culture = null); + IEnumerable Children { get; } + + /// + /// Gets all the children of the content item, regardless of whether they are available for the current culture. + /// + IEnumerable ChildrenForAllCultures { get; } #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 3da690a7e2..fb41c95419 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -58,10 +58,10 @@ namespace Umbraco.Core.Models.PublishedContent public virtual int Id => _content.Id; /// - public virtual string Name(string culture = null) => _content.Name(culture); + public virtual string Name => _content.Name; /// - public virtual string UrlSegment(string culture = null) => _content.UrlSegment(culture); + public virtual string UrlSegment => _content.UrlSegment; /// public virtual int SortOrder => _content.SortOrder; @@ -94,10 +94,13 @@ namespace Umbraco.Core.Models.PublishedContent public virtual DateTime UpdateDate => _content.UpdateDate; /// - public DateTime CultureDate(string culture = null) => _content.CultureDate(culture); + public virtual string Url => _content.Url; /// - public IReadOnlyCollection Cultures => _content.Cultures; + public IReadOnlyDictionary Cultures => _content.Cultures; + + /// + public virtual PublishedItemType ItemType => _content.ItemType; /// public virtual bool IsDraft(string culture = null) => _content.IsDraft(culture); @@ -110,10 +113,13 @@ namespace Umbraco.Core.Models.PublishedContent #region Tree /// - public virtual IPublishedContent Parent() => _content.Parent(); + public virtual IPublishedContent Parent => _content.Parent; /// - public virtual IEnumerable Children(string culture = null) => _content.Children(culture); + public virtual IEnumerable Children => _content.Children; + + /// + public virtual IEnumerable ChildrenForAllCultures => _content.ChildrenForAllCultures; #endregion diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs new file mode 100644 index 0000000000..a8cb0cdaf2 --- /dev/null +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core +{ + public static class PublishedContentExtensions + { + private static IVariationContextAccessor VariationContextAccessor => Current.VariationContextAccessor; + + /// + /// Determines whether the content has a culture. + /// + /// Culture is case-insensitive. + public static bool HasCulture(this IPublishedContent content, string culture) + => content.Cultures.ContainsKey(culture ?? string.Empty); + + /// + /// Determines whether the content is invariant, or has a culture. + /// + /// Culture is case-insensitive. + public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) + => !content.ContentType.VariesByCulture() || content.Cultures.ContainsKey(culture ?? ""); + + /// + /// Filters a sequence of to return invariant items, and items that are published for the specified culture. + /// + /// The content items. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null). + internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, string culture = null) + where T : class, IPublishedContent + { + if (contents == null) throw new ArgumentNullException(nameof(contents)); + + culture = culture ?? Current.VariationContextAccessor.VariationContext?.Culture ?? ""; + + // either does not vary by culture, or has the specified culture + return contents.Where(x => !ContentVariationExtensions.VariesByCulture((IPublishedContentType) x.ContentType) || HasCulture(x, culture)); + } + + /// + /// Gets the name of the content item. + /// + /// The content item. + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). + public static string Name(this IPublishedContent content, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) + return "NAME??"; // fixme where should the invariant one come from? should Cultures contain it? + + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + // get + return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.Name : null; + } + + + /// + /// Gets the url segment of the content item. + /// + /// The content item. + /// The specific culture to get the url segment for. If null is used the current culture is used (Default is null). + public static string UrlSegment(this IPublishedContent content, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) + return "URLSEGMENT??"; // fixme where should the invariant one come from? should Cultures contain it? + + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + // get + return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; + } + + /// + /// Gets the culture date of the content item. + /// + /// The content item. + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). + public static DateTime CultureDate(this IPublishedContent content, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) + return content.UpdateDate; + + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + // get + return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue; + } + + + /// + /// Gets the children of the content item. + /// + /// The content item. + /// The specific culture to get the url children for. If null is used the current culture is used (Default is null). + /// + /// Gets children that are available for the specified culture. + /// Children are sorted by their sortOrder. + /// The '*' culture and supported and returns everything. + /// + public static IEnumerable Children(this IPublishedContent content, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture() && culture != "*") + culture = ""; + + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + var children = content.ChildrenForAllCultures; + return culture == "*" + ? children + : children.Where(x => x.IsInvariantOrHasCulture(culture)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ae1c643b2b..d923065438 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -252,6 +252,7 @@ + diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index d31401c725..f3d9f895ef 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -108,10 +108,10 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(mRoot.ContentType.Alias, publishedMedia.ContentType.Alias); Assert.AreEqual(mRoot.ContentType.Id, publishedMedia.ContentType.Id); Assert.AreEqual(mRoot.Level, publishedMedia.Level); - Assert.AreEqual(mRoot.Name, publishedMedia.Name()); + Assert.AreEqual(mRoot.Name, publishedMedia.Name); Assert.AreEqual(mRoot.Path, publishedMedia.Path); Assert.AreEqual(mRoot.SortOrder, publishedMedia.SortOrder); - Assert.IsNull(publishedMedia.Parent()); + Assert.IsNull(publishedMedia.Parent); } [TestCase("id")] @@ -172,9 +172,9 @@ namespace Umbraco.Tests.Cache.PublishedCache child1, child2 }); - Assert.AreEqual(2, dicDoc.Children().Count()); - Assert.AreEqual(222333, dicDoc.Children().ElementAt(0).Id); - Assert.AreEqual(444555, dicDoc.Children().ElementAt(1).Id); + Assert.AreEqual(2, dicDoc.Children.Count()); + Assert.AreEqual(222333, dicDoc.Children.ElementAt(0).Id); + Assert.AreEqual(444555, dicDoc.Children.ElementAt(1).Id); } [Test] @@ -212,7 +212,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result)); DoAssert(doc, 1234, key, null, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", 0, 0, "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2); - Assert.AreEqual(null, doc.Parent()); + Assert.AreEqual(null, doc.Parent); } [Test] @@ -228,10 +228,10 @@ namespace Umbraco.Tests.Cache.PublishedCache var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true)); DoAssert(doc, 2000, key, null, 2, "image1", "Image", 23, "Shannon", "Shannon", 33, 33, "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1); - Assert.AreEqual(null, doc.Parent()); - Assert.AreEqual(2, doc.Children().Count()); - Assert.AreEqual(2001, doc.Children().ElementAt(0).Id); - Assert.AreEqual(2002, doc.Children().ElementAt(1).Id); + Assert.AreEqual(null, doc.Parent); + Assert.AreEqual(2, doc.Children.Count()); + Assert.AreEqual(2001, doc.Children.ElementAt(0).Id); + Assert.AreEqual(2002, doc.Children.ElementAt(1).Id); } private XmlDocument GetMediaXml() @@ -395,7 +395,7 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(keyVal, doc.Key); Assert.AreEqual(templateIdVal, doc.TemplateId); Assert.AreEqual(sortOrderVal, doc.SortOrder); - Assert.AreEqual(urlNameVal, doc.UrlSegment()); + Assert.AreEqual(urlNameVal, doc.UrlSegment); Assert.AreEqual(nodeTypeAliasVal, doc.ContentType.Alias); Assert.AreEqual(nodeTypeIdVal, doc.ContentType.Id); Assert.AreEqual(writerNameVal, doc.WriterName); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index 3a39d23ccb..db8dc38d6d 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -136,7 +136,12 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private readonly Func _getProperty; private readonly IAppCache _appCache; - public override IPublishedContent Parent() => _getParent.Value; + /// + /// Returns 'Media' as the item type + /// + public override PublishedItemType ItemType => PublishedItemType.Media; + + public override IPublishedContent Parent => _getParent.Value; public int ParentId { get; private set; } @@ -148,15 +153,12 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override int SortOrder => _sortOrder; - public override string Name(string culture = null) => _name; + public override string Name => _name; - public override DateTime CultureDate(string culture = null) => UpdateDate; + private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); + public override IReadOnlyDictionary Cultures => NoCultures.Value; - // ReSharper disable once CollectionNeverUpdated.Local - private static readonly List EmptyListOfString = new List(); - public override IReadOnlyCollection Cultures => EmptyListOfString; - - public override string UrlSegment(string culture = null) => _urlName; + public override string UrlSegment => _urlName; public override string WriterName => _creatorName; @@ -180,7 +182,9 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache public override IEnumerable Properties => _properties; - public override IEnumerable Children(string culture = null) => _getChildren.Value; + public override IEnumerable Children => _getChildren.Value; + + public override IEnumerable ChildrenForAllCultures => Children; public override IPublishedProperty GetProperty(string alias) { diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 33348d071a..8de0209c69 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -268,14 +268,14 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache pathParts.Add(urlName); // move to parent node - n = n.Parent(); + n = n.Parent; hasDomains = n != null && _domainCache.HasAssigned(n.Id); } // no domain, respect HideTopLevelNodeFromPath for legacy purposes if (hasDomains == false && _globalSettings.HideTopLevelNodeFromPath) { - if (node.Parent() == null) + if (node.Parent == null) { var rootNode = GetByRoute(preview, "/", true); if (rootNode == null) diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index 2edb2bae32..4a60912757 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -71,12 +71,17 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private bool _isDraft; - public override IEnumerable Children(string culture = null) + public override IEnumerable Children { - EnsureNodeInitialized(andChildren: true); - return _children; + get + { + EnsureNodeInitialized(andChildren: true); + return _children; + } } + public override IEnumerable ChildrenForAllCultures => Children; + public override IPublishedProperty GetProperty(string alias) { EnsureNodeInitialized(); @@ -84,10 +89,15 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return _properties.TryGetValue(alias, out property) ? property : null; } - public override IPublishedContent Parent() + public override PublishedItemType ItemType => PublishedItemType.Content; + + public override IPublishedContent Parent { - EnsureNodeInitialized(andParent: true); - return _parent; + get + { + EnsureNodeInitialized(andParent: true); + return _parent; + } } public override int Id @@ -126,17 +136,17 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } - public override string Name(string culture = null) + public override string Name { - EnsureNodeInitialized(); - return _name; + get + { + EnsureNodeInitialized(); + return _name; + } } - public override DateTime CultureDate(string culture = null) => UpdateDate; - - // ReSharper disable once CollectionNeverUpdated.Local - private static readonly List EmptyListOfString = new List(); - public override IReadOnlyCollection Cultures => EmptyListOfString; + private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); + public override IReadOnlyDictionary Cultures => NoCultures.Value; public override string WriterName { @@ -201,10 +211,13 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } - public override string UrlSegment(string culture = null) + public override string UrlSegment { - EnsureNodeInitialized(); - return _urlName; + get + { + EnsureNodeInitialized(); + return _urlName; + } } public override int Level diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index a1b736ae90..432247b09f 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -271,10 +271,12 @@ namespace Umbraco.Tests.Published } // ReSharper disable UnassignedGetOnlyAutoProperty + public override PublishedItemType ItemType { get; } public override bool IsDraft(string culture = null) => false; public override bool IsPublished(string culture = null) => true; - public override IPublishedContent Parent() => null; - public override IEnumerable Children(string culture = null) => Enumerable.Empty(); + public override IPublishedContent Parent { get; } + public override IEnumerable Children { get; } + public override IEnumerable ChildrenForAllCultures => Children; public override IPublishedContentType ContentType { get; } // ReSharper restore UnassignedGetOnlyAutoProperty @@ -282,10 +284,9 @@ namespace Umbraco.Tests.Published public override int Id { get; } public override int? TemplateId { get; } public override int SortOrder { get; } - public override string Name(string culture = null) => default; - public override DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public override IReadOnlyCollection Cultures => throw new NotSupportedException(); - public override string UrlSegment(string culture = null) => default; + public override string Name { get; } + public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public override string UrlSegment { get; } public override string WriterName { get; } public override string CreatorName { get; } public override int WriterId { get; } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index d9cb8dbbc2..d77d1e4701 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -231,22 +231,22 @@ namespace Umbraco.Tests.PublishedContent var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; - Assert.IsNull(snapshot.Content.GetById(1).Parent()); - Assert.IsNull(snapshot.Content.GetById(2).Parent()); - Assert.IsNull(snapshot.Content.GetById(3).Parent()); + Assert.IsNull(snapshot.Content.GetById(1).Parent); + Assert.IsNull(snapshot.Content.GetById(2).Parent); + Assert.IsNull(snapshot.Content.GetById(3).Parent); - Assert.AreEqual(1, snapshot.Content.GetById(4).Parent()?.Id); - Assert.AreEqual(1, snapshot.Content.GetById(5).Parent()?.Id); - Assert.AreEqual(1, snapshot.Content.GetById(6).Parent()?.Id); + Assert.AreEqual(1, snapshot.Content.GetById(4).Parent?.Id); + Assert.AreEqual(1, snapshot.Content.GetById(5).Parent?.Id); + Assert.AreEqual(1, snapshot.Content.GetById(6).Parent?.Id); - Assert.AreEqual(2, snapshot.Content.GetById(7).Parent()?.Id); - Assert.AreEqual(2, snapshot.Content.GetById(8).Parent()?.Id); - Assert.AreEqual(2, snapshot.Content.GetById(9).Parent()?.Id); + Assert.AreEqual(2, snapshot.Content.GetById(7).Parent?.Id); + Assert.AreEqual(2, snapshot.Content.GetById(8).Parent?.Id); + Assert.AreEqual(2, snapshot.Content.GetById(9).Parent?.Id); - Assert.AreEqual(3, snapshot.Content.GetById(10).Parent()?.Id); + Assert.AreEqual(3, snapshot.Content.GetById(10).Parent?.Id); - Assert.AreEqual(4, snapshot.Content.GetById(11).Parent()?.Id); - Assert.AreEqual(4, snapshot.Content.GetById(12).Parent()?.Id); + Assert.AreEqual(4, snapshot.Content.GetById(11).Parent?.Id); + Assert.AreEqual(4, snapshot.Content.GetById(12).Parent?.Id); } [Test] @@ -288,7 +288,7 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetById(3).Children().ToArray(); AssertDocuments(documents); - Assert.IsNull(snapshot.Content.GetById(10).Parent()); + Assert.IsNull(snapshot.Content.GetById(10).Parent); } [Test] @@ -330,7 +330,7 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetById(10).Children().ToArray(); AssertDocuments(documents, "N1"); - Assert.AreEqual(10, snapshot.Content.GetById(1).Parent()?.Id); + Assert.AreEqual(10, snapshot.Content.GetById(1).Parent?.Id); } [Test] @@ -509,7 +509,7 @@ namespace Umbraco.Tests.PublishedContent documents = snapshot.Content.GetById(2).Children().ToArray(); AssertDocuments(documents, "N9", "N8"); - Assert.AreEqual(1, snapshot.Content.GetById(7).Parent()?.Id); + Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id); } private void AssertDocuments(IPublishedContent[] documents, params string[] names) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index e4bf02fec0..5c22295547 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -10,9 +10,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web; +using PublishedContentExtensions = Umbraco.Web.PublishedContentExtensions; namespace Umbraco.Tests.PublishedContent { @@ -97,7 +97,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetContent(true, 1); //change a doc type alias - var c = (TestPublishedContent)doc.Children().ElementAt(0); + var c = (TestPublishedContent)doc.Children.ElementAt(0); c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -139,6 +139,8 @@ namespace Umbraco.Tests.PublishedContent TemplateId = 5, UpdateDate = DateTime.Now, Path = "-1,3", + UrlSegment = "home-page", + Name = "Page" + Guid.NewGuid().ToString(), Version = Guid.NewGuid(), WriterId = 1, WriterName = "Shannon", @@ -146,8 +148,6 @@ namespace Umbraco.Tests.PublishedContent Level = 1, Children = new List() }; - d.SetName("Page" + Guid.NewGuid()); - d.SetUrlSegment("home-page"); d.Properties = new Collection(new List { new RawValueProperty(factory.CreatePropertyType("property1", 1), d, "value" + indexVals), @@ -183,26 +183,16 @@ namespace Umbraco.Tests.PublishedContent // l8tr... private class TestPublishedContent : IPublishedContent { - private readonly Dictionary _names = new Dictionary(); - private readonly Dictionary _urlSegments = new Dictionary(); - - public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => default; - - IPublishedContent IPublishedContent.Parent() => Parent; - - IEnumerable IPublishedContent.Children(string culture = null) => Children; - + public string Url { get; set; } + public PublishedItemType ItemType { get; set; } public IPublishedContent Parent { get; set; } public int Id { get; set; } public Guid Key { get; set; } public int? TemplateId { get; set; } public int SortOrder { get; set; } - public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; - public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; - public DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public IReadOnlyCollection Cultures => throw new NotSupportedException(); - public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; - public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; + public string Name { get; set; } + public IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public string UrlSegment { get; set; } public string WriterName { get; set; } public string CreatorName { get; set; } public int WriterId { get; set; } @@ -218,6 +208,7 @@ namespace Umbraco.Tests.PublishedContent public IEnumerable Properties { get; set; } public IEnumerable Children { get; set; } + public IEnumerable ChildrenForAllCultures => Children; public IPublishedProperty GetProperty(string alias) { @@ -232,7 +223,7 @@ namespace Umbraco.Tests.PublishedContent IPublishedContent content = this; while (content != null && (property == null || property.HasValue() == false)) { - content = content.Parent(); + content = content.Parent; property = content == null ? null : content.GetProperty(alias); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index dc726c5748..62447742ff 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -122,8 +122,11 @@ namespace Umbraco.Tests.PublishedContent { Id = 1, SortOrder = 0, + Name = "Content 1", + UrlSegment = "content-1", Path = "/1", Level = 1, + Url = "/content-1", ParentId = -1, ChildIds = new[] { 2 }, Properties = new Collection @@ -131,16 +134,16 @@ namespace Umbraco.Tests.PublishedContent prop1, prop2, noprop } }; - item1.SetName("Content 1"); - item1.SetUrlSegment("content-1"); - item1.SetUrl("/content-1"); var item2 = new SolidPublishedContent(contentType1) { Id = 2, SortOrder = 0, + Name = "Content 2", + UrlSegment = "content-2", Path = "/1/2", Level = 2, + Url = "/content-1/content-2", ParentId = 1, ChildIds = new int[] { 3 }, Properties = new Collection @@ -148,9 +151,6 @@ namespace Umbraco.Tests.PublishedContent prop3 } }; - item2.SetName("Content 2"); - item2.SetUrlSegment("content-2"); - item2.SetUrl("/content-1/content-2"); var prop4 = new SolidPublishedPropertyWithLanguageVariants { @@ -164,8 +164,11 @@ namespace Umbraco.Tests.PublishedContent { Id = 3, SortOrder = 0, + Name = "Content 3", + UrlSegment = "content-3", Path = "/1/2/3", Level = 3, + Url = "/content-1/content-2/content-3", ParentId = 2, ChildIds = new int[] { }, Properties = new Collection @@ -173,15 +176,12 @@ namespace Umbraco.Tests.PublishedContent prop4 } }; - item3.SetName("Content 3"); - item3.SetUrlSegment("content-3"); - item3.SetUrl("/content-1/content-2/content-3"); - item1.SetChildren(new List { item2 }); - item2.SetParent(item1); + item1.Children = new List { item2 }; + item2.Parent = item1; - item2.SetChildren(new List { item3 }); - item3.SetParent(item2); + item2.Children = new List { item3 }; + item3.Parent = item2; cache.Add(item1); cache.Add(item2); @@ -247,7 +247,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText2"); Assert.IsNull(value); } @@ -255,7 +255,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); } @@ -263,7 +263,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Get_Content_Recursively_Unless_Requested2() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2"); Assert.IsNull(value); @@ -272,7 +272,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively2() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); Assert.IsNull(content.GetProperty("welcomeText2")); var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors); Assert.AreEqual("Welcome", value); @@ -281,7 +281,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_Recursively3() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First().Children.First(); Assert.IsNull(content.GetProperty("noprop")); var value = content.Value("noprop", fallback: Fallback.ToAncestors); // property has no value but we still get the value (ie, the converter would do something) @@ -292,7 +292,7 @@ namespace Umbraco.Tests.PublishedContent public void Can_Get_Content_With_Recursive_Priority() { Current.VariationContextAccessor.VariationContext = new VariationContext("nl"); - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language)); @@ -303,7 +303,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Get_Content_With_Fallback_Language_Priority() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); var value = content.Value("welcomeText", "nl", fallback: Fallback.ToLanguage); // No Dutch value is directly assigned. Check has fallen back to English value from language variant. @@ -313,14 +313,14 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Throws_For_Non_Supported_Fallback() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); Assert.Throws(() => content.Value("welcomeText", "nl", fallback: Fallback.To(999))); } [Test] public void Can_Fallback_To_Default_Value() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); // no Dutch value is assigned, so getting null var value = content.Value("welcomeText", "nl"); @@ -338,7 +338,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Can_Have_Custom_Default_Value() { - var content = Current.UmbracoContext.Content.GetAtRoot().First().Children().First(); + var content = Current.UmbracoContext.Content.GetAtRoot().First().Children.First(); // HACK: the value, pretend the converter would return something var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index abd4771547..440474ae74 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -29,8 +29,11 @@ namespace Umbraco.Tests.PublishedContent { Id = 1, SortOrder = 0, + Name = "Content 1", + UrlSegment = "content-1", Path = "/1", Level = 1, + Url = "/content-1", ParentId = -1, ChildIds = new int[] { }, Properties = new Collection @@ -44,17 +47,17 @@ namespace Umbraco.Tests.PublishedContent } } }; - content.SetName("Content 1"); - content.SetUrlSegment("content-1"); - content.SetUrl("/content-1"); cache.Add(content); content = new SolidPublishedContent(contentType2) { Id = 2, SortOrder = 1, + Name = "Content 2", + UrlSegment = "content-2", Path = "/2", Level = 1, + Url = "/content-2", ParentId = -1, ChildIds = new int[] { }, Properties = new Collection @@ -68,17 +71,17 @@ namespace Umbraco.Tests.PublishedContent } } }; - content.SetName("Content 2"); - content.SetUrlSegment("content-2"); - content.SetUrl("/content-2"); cache.Add(content); content = new SolidPublishedContent(contentType2Sub) { Id = 3, SortOrder = 2, + Name = "Content 2Sub", + UrlSegment = "content-2sub", Path = "/3", Level = 1, + Url = "/content-2sub", ParentId = -1, ChildIds = new int[] { }, Properties = new Collection @@ -92,9 +95,6 @@ namespace Umbraco.Tests.PublishedContent } } }; - content.SetName("Content 2Sub"); - content.SetUrlSegment("content-2sub"); - content.SetUrl("/content-2sub"); cache.Add(content); } @@ -114,17 +114,17 @@ namespace Umbraco.Tests.PublishedContent .ToIndexedArray(); var item = items[0]; - Assert.AreEqual("Content 1", item.Content.Name()); + Assert.AreEqual("Content 1", item.Content.Name); Assert.IsTrue(item.IsFirst()); Assert.IsFalse(item.IsLast()); item = items[1]; - Assert.AreEqual("Content 2", item.Content.Name()); + Assert.AreEqual("Content 2", item.Content.Name); Assert.IsFalse(item.IsFirst()); Assert.IsFalse(item.IsLast()); item = items[2]; - Assert.AreEqual("Content 2Sub", item.Content.Name()); + Assert.AreEqual("Content 2Sub", item.Content.Name); Assert.IsFalse(item.IsFirst()); Assert.IsTrue(item.IsLast()); } @@ -157,7 +157,7 @@ namespace Umbraco.Tests.PublishedContent var content = Current.UmbracoContext.Content.GetAtRoot() .OfType() .First(x => x.Prop1 == 1234); - Assert.AreEqual("Content 2", content.Name()); + Assert.AreEqual("Content 2", content.Name); Assert.AreEqual(1234, content.Prop1); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index c2cf22f85f..b789eb0ef8 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -416,13 +416,13 @@ namespace Umbraco.Tests.PublishedContent var mSubChild3 = MakeNewMedia("SubChild3", mType, user, mChild1.Id); var publishedRoot = GetNode(mRoot.Id); - Assert.AreEqual(null, publishedRoot.Parent()); + Assert.AreEqual(null, publishedRoot.Parent); var publishedChild1 = GetNode(mChild1.Id); - Assert.AreEqual(mRoot.Id, publishedChild1.Parent().Id); + Assert.AreEqual(mRoot.Id, publishedChild1.Parent.Id); var publishedSubChild1 = GetNode(mSubChild1.Id); - Assert.AreEqual(mChild1.Id, publishedSubChild1.Parent().Id); + Assert.AreEqual(mChild1.Id, publishedSubChild1.Parent.Id); } [Test] diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index ea3a00371a..d8dbabb569 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -58,13 +58,13 @@ namespace Umbraco.Tests.PublishedContent { var pc = new Mock(); pc.Setup(content => content.Id).Returns(1); - pc.Setup(content => content.Name(It.IsAny())).Returns("test"); + pc.Setup(content => content.Name).Returns("test"); pc.Setup(content => content.WriterName).Returns("admin"); pc.Setup(content => content.CreatorName).Returns("admin"); pc.Setup(content => content.CreateDate).Returns(DateTime.Now); pc.Setup(content => content.UpdateDate).Returns(DateTime.Now); pc.Setup(content => content.Path).Returns("-1,1"); - pc.Setup(content => content.Parent()).Returns(() => null); + pc.Setup(content => content.Parent).Returns(() => null); pc.Setup(content => content.Properties).Returns(new Collection()); pc.Setup(content => content.ContentType).Returns(new PublishedContentType(22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); return pc; diff --git a/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs b/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs index be94923ad0..4aad3d0acb 100644 --- a/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs +++ b/src/Umbraco.Tests/PublishedContent/RootNodeTests.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.PublishedContent content = ctx.Content.GetById(1046); Assert.IsNotNull(content); Assert.AreEqual(1, content.Level); - Assert.IsNull(content.Parent()); + Assert.IsNull(content.Parent); // non-existing content is null content = ctx.Content.GetById(666); diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index fbb2a7549b..ab5a65146a 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -102,7 +102,7 @@ namespace Umbraco.Tests.PublishedContent public override IEnumerable GetAtRoot(bool preview) { - return _content.Values.Where(x => x.Parent() == null); + return _content.Values.Where(x => x.Parent == null); } public override IPublishedContent GetSingleByXPath(bool preview, string xpath, Core.Xml.XPathVariable[] vars) @@ -158,10 +158,6 @@ namespace Umbraco.Tests.PublishedContent internal class SolidPublishedContent : IPublishedContent { - private readonly Dictionary _names = new Dictionary(); - private readonly Dictionary _urlSegments = new Dictionary(); - private readonly Dictionary _urls = new Dictionary(); - #region Constructor public SolidPublishedContent(IPublishedContentType contentType) @@ -184,12 +180,10 @@ namespace Umbraco.Tests.PublishedContent public Guid Key { get; set; } public int? TemplateId { get; set; } public int SortOrder { get; set; } - public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; - public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; - public DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public IReadOnlyCollection Cultures => throw new NotSupportedException(); - public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; - public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; + public string Name { get; set; } + public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); + public IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public string UrlSegment { get; set; } public string WriterName { get; set; } public string CreatorName { get; set; } public int WriterId { get; set; } @@ -199,9 +193,9 @@ namespace Umbraco.Tests.PublishedContent public DateTime UpdateDate { get; set; } public Guid Version { get; set; } public int Level { get; set; } - public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => _urls.TryGetValue(culture ?? "", out var url) ? url : null; - public void SetUrl(string url, string culture = null) => _urls[culture ?? ""] = url; + public string Url { get; set; } + public PublishedItemType ItemType { get { return PublishedItemType.Content; } } public bool IsDraft(string culture = null) => false; public bool IsPublished(string culture = null) => true; @@ -212,12 +206,9 @@ namespace Umbraco.Tests.PublishedContent public int ParentId { get; set; } public IEnumerable ChildIds { get; set; } - private IPublishedContent _parent; - public IPublishedContent Parent() => _parent; - public void SetParent(IPublishedContent parent) => _parent = parent; - private IEnumerable _children; - public IEnumerable Children(string culture = null) => _children; - public void SetChildren(IEnumerable children) => _children = children; + public IPublishedContent Parent { get; set; } + public IEnumerable Children { get; set; } + public IEnumerable ChildrenForAllCultures => Children; #endregion @@ -244,7 +235,7 @@ namespace Umbraco.Tests.PublishedContent IPublishedContent content = this; while (content != null && (property == null || property.HasValue() == false)) { - content = content.Parent(); + content = content.Parent; property = content == null ? null : content.GetProperty(alias); } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 1fb090e220..62f93d106f 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -7,38 +7,20 @@ namespace Umbraco.Tests.TestHelpers.Stubs { internal class TestPublishedContent : PublishedElement, IPublishedContent { - private readonly Dictionary _names = new Dictionary(); - private readonly Dictionary _urlSegments = new Dictionary(); - private readonly Dictionary _cultures; - - public TestPublishedContent(IPublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) + public TestPublishedContent(IPublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) : base(contentType, key, values, previewing) { Id = id; - _cultures = cultures ?? new Dictionary(); + Cultures = cultures ?? new Dictionary(); } public int Id { get; } public int? TemplateId { get; set; } public int SortOrder { get; set; } - public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; - public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; + public string Name { get; set; } public IVariationContextAccessor VariationContextAccessor { get; set; } - public DateTime CultureDate(string culture = null) - { - // handle context culture - if (culture == null) - culture = VariationContextAccessor?.VariationContext?.Culture; - - // no invariant culture infos - if (culture == "" || Cultures == null) return UpdateDate; - - // get - return _cultures.TryGetValue(culture, out var date) ? date : DateTime.MinValue; - } - public IReadOnlyCollection Cultures { get; set; } - public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; - public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; + public IReadOnlyDictionary Cultures { get; set; } + public string UrlSegment { get; set; } public string DocumentTypeAlias => ContentType.Alias; public int DocumentTypeId { get; set; } public string WriterName { get; set; } @@ -50,15 +32,13 @@ namespace Umbraco.Tests.TestHelpers.Stubs public DateTime UpdateDate { get; set; } public Guid Version { get; set; } public int Level { get; set; } - public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => throw new NotSupportedException(); + public string Url { get; set; } + public PublishedItemType ItemType => ContentType.ItemType; public bool IsDraft(string culture = null) => false; public bool IsPublished(string culture = null) => true; - private IPublishedContent _parent; - public IPublishedContent Parent() => _parent; - public void SetParent(IPublishedContent parent) => _parent = parent; - private IEnumerable _children; - public IEnumerable Children(string culture = null) => _children; - public void SetChildren(IEnumerable children) => _children = children; + public IPublishedContent Parent { get; set; } + public IEnumerable Children { get; set; } + public IEnumerable ChildrenForAllCultures => Children; // copied from PublishedContentBase public IPublishedProperty GetProperty(string alias, bool recurse) @@ -70,7 +50,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs var firstNonNullProperty = property; while (content != null && (property == null || property.HasValue() == false)) { - content = content.Parent(); + content = content.Parent; property = content?.GetProperty(alias); if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; } diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index c0a6e97dda..3a5405548b 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -99,7 +99,7 @@ namespace Umbraco.Tests.Web var snapshotService = Mock.Of(); Mock.Get(snapshotService).Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(snapshot); var media = Mock.Of(); - Mock.Get(media).Setup(x => x.Url(It.IsAny(), It.IsAny())).Returns("/media/1001/my-image.jpg"); + Mock.Get(media).Setup(x => x.Url).Returns("/media/1001/my-image.jpg"); var mediaCache = Mock.Of(); Mock.Get(mediaCache).Setup(x => x.GetById(It.IsAny())).Returns(media); diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index c1c85a9688..e8a9d6b1f7 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1222,7 +1222,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", "dev": true }, "asap": { @@ -1284,7 +1284,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", "dev": true }, "asynckit": { @@ -1536,7 +1536,7 @@ "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", "dev": true, "requires": { "readable-stream": "^2.3.5", @@ -1551,7 +1551,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -1566,7 +1566,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -1740,7 +1740,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -1750,7 +1750,7 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { @@ -2516,7 +2516,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", "dev": true }, "continuable-cache": { @@ -4045,7 +4045,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -4061,7 +4061,7 @@ }, "engine.io-client": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", "dev": true, "requires": { @@ -4081,7 +4081,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -4476,13 +4476,13 @@ "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=", "dev": true }, "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -5244,7 +5244,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true }, "fs-extra": { @@ -5309,8 +5309,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5331,14 +5330,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5353,20 +5350,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5483,8 +5477,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5496,7 +5489,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5511,7 +5503,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5519,14 +5510,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5545,7 +5534,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5626,8 +5614,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5639,7 +5626,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5725,8 +5711,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5762,7 +5747,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5782,7 +5766,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5826,14 +5809,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -7575,7 +7556,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", "dev": true, "requires": { "isarray": "2.0.1" @@ -7778,7 +7759,7 @@ "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", "dev": true, "requires": { "eventemitter3": "^3.0.0", @@ -9335,7 +9316,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true }, "lpad-align": { @@ -9369,7 +9350,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "requires": { "pify": "^3.0.0" @@ -12735,7 +12716,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, @@ -13616,7 +13597,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", "dev": true }, "qs": { @@ -14775,7 +14756,7 @@ }, "socket.io-parser": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", "dev": true, "requires": { @@ -14787,7 +14768,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -14920,7 +14901,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "optional": true, @@ -15328,7 +15309,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "requires": { "escape-string-regexp": "^1.0.2" @@ -15488,7 +15469,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15731,7 +15712,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true }, "to-fast-properties": { @@ -15860,7 +15841,7 @@ "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", "dev": true, "requires": { "media-typer": "0.3.0", @@ -15902,7 +15883,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", "dev": true }, "unc-path-regex": { @@ -16505,7 +16486,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", "dev": true, "requires": { "async-limiter": "~1.0.0", diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml index 495141f821..46c8de695c 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml @@ -19,7 +19,7 @@ { @* Get the starting page *@ var startNode = Umbraco.Content(startNodeId); - var selection = startNode.Children().Where(x => x.IsVisible()).ToArray(); + var selection = startNode.Children.Where(x => x.IsVisible()).ToArray(); if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml index 6ba7471899..e6606d6204 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml @@ -9,7 +9,7 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).ToArray(); } +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).ToArray(); } @if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml index 4ace3da7d1..2c2cc4422b 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml @@ -10,7 +10,7 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).OrderByDescending(x => x.CreateDate).ToArray(); } +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderByDescending(x => x.CreateDate).ToArray(); } @if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml index ecfd8ebe9d..d0398e7272 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml @@ -10,7 +10,7 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).OrderBy(x => x.Name).ToArray(); } +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderBy(x => x.Name).ToArray(); } @if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml index 667a384dfe..1bffae04c4 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml @@ -17,7 +17,7 @@ @if (propertyAlias != null) { - var selection = Model.Content.Children().Where(x => x.IsVisible()).OrderBy(x => x.Value(propertyAlias.ToString())).ToArray(); + var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderBy(x => x.Value(propertyAlias.ToString())).ToArray(); if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml index 196db52d92..7ae917b41d 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml @@ -7,7 +7,7 @@ the page currently being viewed by the website visitor, displayed as nested unordered HTML lists. *@ -@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).ToArray(); } +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).ToArray(); } @* Ensure that the Current Page has children *@ @if (selection.Length > 0) @@ -25,7 +25,7 @@ @* if this child page has any children, where the property umbracoNaviHide is not True *@ @{ - var children = item.Children().Where(x => x.IsVisible()).ToArray(); + var children = item.Children.Where(x => x.IsVisible()).ToArray(); if (children.Length > 0) { @* Call our helper to display the children *@ @@ -54,7 +54,7 @@ @* if the page has any children, where the property umbracoNaviHide is not True *@ @{ - var children = item.Children().Where(x => x.IsVisible()).ToArray(); + var children = item.Children.Where(x => x.IsVisible()).ToArray(); if (children.Length > 0) { @* Recurse and call our helper to display the children *@ diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml index b6067ff93a..51fdbadb00 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml @@ -18,7 +18,7 @@ { @* Get the media item associated with the id passed in *@ var media = Umbraco.Media(mediaId); - var selection = media.Children().ToArray(); + var selection = media.Children.ToArray(); if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml index 733c6be801..1c01eeb855 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Navigation.cshtml @@ -7,7 +7,7 @@ It also highlights the current active page/section in the navigation with the CSS class "current". *@ -@{ var selection = Model.Content.Root().Children().Where(x => x.IsVisible()).ToArray(); } +@{ var selection = Model.Content.Root().Children.Where(x => x.IsVisible()).ToArray(); } @if (selection.Length > 0) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml index a14d6fdc09..567ed5d07d 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml @@ -23,7 +23,7 @@ const int maxLevelForSitemap = 4; @* Select visible children *@ - var selection = node.Children().Where(x => x.IsVisible() && x.Level <= maxLevelForSitemap).ToArray(); + var selection = node.Children.Where(x => x.IsVisible() && x.Level <= maxLevelForSitemap).ToArray(); @* If any items are returned, render a list *@ if (selection.Length > 0) diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index 02130da8b5..2980b4a4c0 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.Controllers // if it's not a local url we'll redirect to the root of the current site return Redirect(Url.IsLocalUrl(model.RedirectUrl) ? model.RedirectUrl - : CurrentPage.AncestorOrSelf(1).Url()); + : CurrentPage.AncestorOrSelf(1).Url); } //redirect to current page by default diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index b252f51fae..ed737e7749 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -71,7 +71,7 @@ namespace Umbraco.Web.Editors QueryExpression = queryExpression.ToString(), ResultCount = results.Count, ExecutionTime = timer.ElapsedMilliseconds, - SampleResults = results.Take(20).Select(x => new TemplateQueryResult { Icon = "icon-file", Name = x.Name() }) + SampleResults = results.Take(20).Select(x => new TemplateQueryResult { Icon = "icon-file", Name = x.Name }) }; } @@ -186,12 +186,12 @@ namespace Umbraco.Web.Editors : contents.OrderByDescending(x => x.UpdateDate); case "name": return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Name()) - : contents.OrderByDescending(x => x.Name()); + ? contents.OrderBy(x => x.Name) + : contents.OrderByDescending(x => x.Name); default: return sortExpression.Direction == "ascending" - ? contents.OrderBy(x => x.Name()) - : contents.OrderByDescending(x => x.Name()); + ? contents.OrderBy(x => x.Name) + : contents.OrderByDescending(x => x.Name); } } diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index f641d6ca21..41f0e2fb65 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -35,9 +35,9 @@ namespace Umbraco.Web.Macros throw new ArgumentException("Document request has no node.", nameof(frequest)); PopulatePageData(frequest.PublishedContent.Id, - frequest.PublishedContent.Name(), frequest.PublishedContent.ContentType.Id, frequest.PublishedContent.ContentType.Alias, + frequest.PublishedContent.Name, frequest.PublishedContent.ContentType.Id, frequest.PublishedContent.ContentType.Alias, frequest.PublishedContent.WriterName, frequest.PublishedContent.CreatorName, frequest.PublishedContent.CreateDate, frequest.PublishedContent.UpdateDate, - frequest.PublishedContent.Path, frequest.PublishedContent.Parent()?.Id ?? -1); + frequest.PublishedContent.Path, frequest.PublishedContent.Parent?.Id ?? -1); if (frequest.HasTemplate) { @@ -57,9 +57,9 @@ namespace Umbraco.Web.Macros if (doc == null) throw new ArgumentNullException(nameof(doc)); PopulatePageData(doc.Id, - doc.Name(), doc.ContentType.Id, doc.ContentType.Alias, + doc.Name, doc.ContentType.Id, doc.ContentType.Alias, doc.WriterName, doc.CreatorName, doc.CreateDate, doc.UpdateDate, - doc.Path, doc.Parent()?.Id ?? -1); + doc.Path, doc.Parent?.Id ?? -1); if (doc.TemplateId.HasValue) { @@ -182,8 +182,8 @@ namespace Umbraco.Web.Macros { private readonly IContent _inner; private readonly IPublishedProperty[] _properties; + private IReadOnlyDictionary _cultureInfos; private readonly IVariationContextAccessor _variationContextAccessor; - private readonly IPublishedContent _parent; private static readonly IReadOnlyDictionary NoCultureInfos = new Dictionary(); @@ -215,7 +215,7 @@ namespace Umbraco.Web.Macros .Cast() .ToArray(); - _parent = new PagePublishedContent(_inner.ParentId); + Parent = new PagePublishedContent(_inner.ParentId); } public IPublishedContentType ContentType { get; } @@ -228,37 +228,25 @@ namespace Umbraco.Web.Macros public int SortOrder => _inner.SortOrder; - public string Name(string culture = null) => _inner.GetCultureName(culture); + public string Name => _inner.Name; - public DateTime CultureDate(string culture = null) - { - // invariant has invariant value (whatever the requested culture) - if (!ContentType.VariesByCulture()) - return UpdateDate; - - // handle context culture for variant - if (culture == null) - culture = _variationContextAccessor.VariationContext.Culture; - - // get - return culture != "" && _inner.PublishCultureInfos.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue; - } - - // ReSharper disable once CollectionNeverUpdated.Local - private static readonly List EmptyListOfString = new List(); - private IReadOnlyList _cultures; - - public IReadOnlyCollection Cultures + public IReadOnlyDictionary Cultures { get { if (!_inner.ContentType.VariesByCulture()) - return EmptyListOfString; - return _cultures ?? (_cultures = _inner.PublishCultureInfos.Values.Select(x => x.Culture).ToList()); + return NoCultureInfos; + + if (_cultureInfos != null) + return _cultureInfos; + + var urlSegmentProviders = Current.UrlSegmentProviders; // TODO inject + return _cultureInfos = _inner.PublishCultureInfos.Values + .ToDictionary(x => x.Culture, x => new PublishedCultureInfo(x.Culture, x.Name, _inner.GetUrlSegment(urlSegmentProviders, x.Culture), x.Date)); } } - public string UrlSegment(string culture = null) => throw new NotImplementedException(); + public string UrlSegment => throw new NotImplementedException(); public string WriterName { get; } @@ -276,7 +264,9 @@ namespace Umbraco.Web.Macros public int Level => _inner.Level; - public string Url(string culture = null, UrlMode mode = UrlMode.Auto) => throw new NotSupportedException(); + public string Url => throw new NotImplementedException(); + + public PublishedItemType ItemType => PublishedItemType.Content; public bool IsDraft(string culture = null) { @@ -288,9 +278,11 @@ namespace Umbraco.Web.Macros throw new NotImplementedException(); } - public IPublishedContent Parent() => _parent; + public IPublishedContent Parent { get; } - public IEnumerable Children(string culture = null) => throw new NotImplementedException(); + public IEnumerable Children => throw new NotImplementedException(); + + public IEnumerable ChildrenForAllCultures => throw new NotImplementedException(); public IEnumerable Properties => _properties; diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 6e0246f416..7b467b6d15 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -160,7 +160,7 @@ namespace Umbraco.Web.Models.PublishedContent IPublishedProperty property; // if we are here, content's property has no value do { - content = content.Parent(); + content = content.Parent; var propertyType = content?.ContentType.GetPropertyType(alias); diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 935c594285..cfc4fd1106 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors.ValueConverters; namespace Umbraco.Web.Models { @@ -34,10 +33,10 @@ namespace Umbraco.Web.Models public abstract int Id { get; } /// - public abstract string Name(string culture = null); + public virtual string Name => this.Name(); /// - public abstract string UrlSegment(string culture = null); + public virtual string UrlSegment => this.UrlSegment(); /// public abstract int SortOrder { get; } @@ -70,10 +69,13 @@ namespace Umbraco.Web.Models public abstract DateTime UpdateDate { get; } /// - public abstract DateTime CultureDate(string culture = null); + public virtual string Url => this.Url(); /// - public abstract IReadOnlyCollection Cultures { get; } + public abstract IReadOnlyDictionary Cultures { get; } + + /// + public abstract PublishedItemType ItemType { get; } /// public abstract bool IsDraft(string culture = null); @@ -86,10 +88,13 @@ namespace Umbraco.Web.Models #region Tree /// - public abstract IPublishedContent Parent(); + public abstract IPublishedContent Parent { get; } /// - public abstract IEnumerable Children(string culture = null); + public virtual IEnumerable Children => this.Children(); + + /// + public abstract IEnumerable ChildrenForAllCultures { get; } #endregion diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index c4277d6a79..4dcee5eb24 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -157,7 +157,7 @@ namespace Umbraco.Web.PublishedCache.NuCache pathParts.Add(urlSegment); // move to parent node - n = n.Parent(); + n = n.Parent; if (n != null) urlSegment = n.UrlSegment(culture); @@ -206,7 +206,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // "/foo" fails (looking for "/*/foo") we try also "/foo". // this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but // that's the way it works pre-4.10 and we try to be backward compat for the time being - if (content.Parent() == null) + if (content.Parent == null) { var rootNode = GetByRoute(preview, "/", true); if (rootNode == null) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs index c9dd493ee6..51badc8b9a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.Navigable var i = 0; _builtInValues = new [] { - XmlString(i++, _content.Name()), + XmlString(i++, _content.Name), XmlString(i++, _content.ParentId), XmlString(i++, _content.CreateDate), XmlString(i++, _content.UpdateDate), @@ -28,7 +28,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.Navigable XmlString(i++, _content.TemplateId), XmlString(i++, _content.WriterId), XmlString(i++, _content.CreatorId), - XmlString(i++, _content.UrlSegment()), + XmlString(i++, _content.UrlSegment), XmlString(i, _content.IsDraft()) }; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index c227c75952..67f219be21 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -110,7 +110,7 @@ namespace Umbraco.Web.PublishedCache.NuCache internal static Func GetMediaByIdFunc { get; set; } = (publishedShapshot, previewing, id) => publishedShapshot.Media.GetById(previewing, id); - private Func GetGetterById(PublishedItemType itemType) + private Func GetGetterById() { switch (ContentType.ItemType) { @@ -147,36 +147,6 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override int Id => _contentNode.Id; - /// - public override string Name(string culture = null) - { - // invariant has invariant value (whatever the requested culture) - if (!ContentType.VariesByCulture()) - return ContentData.Name; - - // handle context culture for variant - if (culture == null) - culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - - // get - return culture != "" && ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.Name : null; - } - - /// - public override string UrlSegment(string culture = null) - { - // invariant has invariant value (whatever the requested culture) - if (!ContentType.VariesByCulture()) - return _urlSegment; - - // handle context culture for variant - if (culture == null) - culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - - // get - return ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; - } - /// public override int SortOrder => _contentNode.SortOrder; @@ -207,37 +177,31 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override DateTime UpdateDate => ContentData.VersionDate; - /// - public override DateTime CultureDate(string culture = null) - { - // invariant has invariant value (whatever the requested culture) - if (!ContentType.VariesByCulture()) - return UpdateDate; - - // handle context culture for variant - if (culture == null) - culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - - // get - return culture != "" && ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue; - } - // ReSharper disable once CollectionNeverUpdated.Local - private static readonly List EmptyListOfString = new List(); - private IReadOnlyCollection _cultures; + private static readonly IReadOnlyDictionary EmptyCultures = new Dictionary(); + private IReadOnlyDictionary _cultures; /// - public override IReadOnlyCollection Cultures + public override IReadOnlyDictionary Cultures { get { if (!ContentType.VariesByCulture()) - return EmptyListOfString; + return EmptyCultures; - return _cultures ?? (_cultures = new HashSet(ContentData.CultureInfos.Keys, StringComparer.OrdinalIgnoreCase)); + if (_cultures != null) return _cultures; + + if (ContentData.CultureInfos == null) + throw new Exception("panic: _contentDate.CultureInfos is null."); + + return _cultures = ContentData.CultureInfos + .ToDictionary(x => x.Key, x => new PublishedCultureInfo(x.Key, x.Value.Name, x.Value.UrlSegment, x.Value.Date), StringComparer.OrdinalIgnoreCase); } } + /// + public override PublishedItemType ItemType => _contentNode.ContentType.ItemType; + /// public override bool IsDraft(string culture = null) { @@ -288,38 +252,35 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Tree /// - public override IPublishedContent Parent() + public override IPublishedContent Parent { - var getById = GetGetterById(ContentType.ItemType); - var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; - return getById(publishedSnapshot, IsPreviewing, ParentId); + get + { + var getById = GetGetterById(); + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; + return getById(publishedSnapshot, IsPreviewing, ParentId); + } } /// - public override IEnumerable Children(string culture = null) + public override IEnumerable ChildrenForAllCultures { - // invariant has invariant value (whatever the requested culture) - if (!ContentType.VariesByCulture() && culture != "*") - culture = ""; - - // handle context culture for variant - if (culture == null) - culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; - - var getById = GetGetterById(ContentType.ItemType); - var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; - var id = _contentNode.FirstChildContentId; - - while (id > 0) + get { - var content = getById(publishedSnapshot, IsPreviewing, id); - if (content == null) - throw new Exception("panic: failed to get content"); + var getById = GetGetterById(); + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; + var id = _contentNode.FirstChildContentId; + + while (id > 0) + { + var content = getById(publishedSnapshot, IsPreviewing, id); + if (content == null) + throw new Exception("panic: failed to get content"); - if (culture == "*" || content.IsInvariantOrHasCulture(culture)) yield return content; - id = UnwrapIPublishedContent(content)._contentNode.NextSiblingContentId; + id = UnwrapIPublishedContent(content)._contentNode.NextSiblingContentId; + } } } @@ -381,7 +342,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // includes all children, published or unpublished // NavigableNavigator takes care of selecting those it wants // note: this is not efficient - we do not try to be (would require a double-linked list) - internal IList ChildIds => Children().Select(x => x.Id).ToList(); + internal IList ChildIds => Children.Select(x => x.Id).ToList(); // used by Property // gets a value indicating whether the content or media exists in diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 9c6f60da97..6e9ec61c62 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -73,13 +73,17 @@ namespace Umbraco.Web.PublishedCache #region IPublishedContent + public override PublishedItemType ItemType => PublishedItemType.Member; + public override bool IsDraft(string culture = null) => false; public override bool IsPublished(string culture = null) => true; - public override IPublishedContent Parent() => null; + public override IPublishedContent Parent => null; - public override IEnumerable Children(string culture = null) => Enumerable.Empty(); + public override IEnumerable Children => Enumerable.Empty(); + + public override IEnumerable ChildrenForAllCultures => Enumerable.Empty(); public override IEnumerable Properties => _properties; @@ -129,17 +133,11 @@ namespace Umbraco.Web.PublishedCache public override int SortOrder => 0; - public override string Name(string culture = null) - { - // member name does not vary, ignore culture - return _member.Name; - } + public override string Name => _member.Name; - public override DateTime CultureDate(string culture = null) => throw new NotSupportedException(); + public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public override IReadOnlyCollection Cultures => throw new NotSupportedException(); - - public override string UrlSegment(string culture = null) => throw new NotSupportedException(); + public override string UrlSegment => throw new NotSupportedException(); // TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current public override string WriterName => _member.GetCreatorProfile().Name; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index eb38826193..fbfc52f4d8 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -167,36 +167,6 @@ namespace Umbraco.Web #region Variations - /// - /// Determines whether the content has a culture. - /// - /// Culture is case-insensitive. - public static bool HasCulture(this IPublishedContent content, string culture) - => content.Cultures.Contains(culture ?? string.Empty); - - /// - /// Determines whether the content is invariant, or has a culture. - /// - /// Culture is case-insensitive. - public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) - => !content.ContentType.VariesByCulture() || content.Cultures.Contains(culture ?? ""); - - /// - /// Filters a sequence of to return invariant items, and items that are published for the specified culture. - /// - /// The content items. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null). - internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, string culture = null) - where T : class, IPublishedContent - { - if (contents == null) throw new ArgumentNullException(nameof(contents)); - - culture = culture ?? Current.VariationContextAccessor.VariationContext?.Culture ?? ""; - - // either does not vary by culture, or has the specified culture - return contents.Where(x => !x.ContentType.VariesByCulture() || x.HasCulture(culture)); - } - /// /// Gets the culture assigned to a document by domains, in the context of a current Uri. /// @@ -555,7 +525,7 @@ namespace Umbraco.Web /// This method is here for consistency purposes but does not make much sense. public static IPublishedContent Ancestor(this IPublishedContent content) { - return content.Parent(); + return content.Parent; } /// @@ -687,7 +657,7 @@ namespace Umbraco.Web { if (content == null) throw new ArgumentNullException(nameof(content)); if (orSelf) yield return content; - while ((content = content.Parent()) != null) + while ((content = content.Parent) != null) yield return content; } @@ -891,7 +861,7 @@ namespace Umbraco.Web where T : class, IPublishedContent { if (content == null) throw new ArgumentNullException(nameof(content)); - return content.Parent() as T; + return content.Parent as T; } #endregion @@ -1111,8 +1081,8 @@ namespace Umbraco.Web /// The siblings of the content including the node itself. public static IEnumerable SiblingsAndSelf(this IPublishedContent content, string culture = null) { - return content.Parent() != null - ? content.Parent().Children(culture) + return content.Parent != null + ? content.Parent.Children(culture) : PublishedSnapshot.Content.GetAtRoot().WhereIsInvariantOrHasCulture(culture); } @@ -1125,8 +1095,8 @@ namespace Umbraco.Web /// The siblings of the content including the node itself, of the given content type. public static IEnumerable SiblingsAndSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) { - return content.Parent() != null - ? content.Parent().ChildrenOfType(contentTypeAlias, culture) + return content.Parent != null + ? content.Parent.ChildrenOfType(contentTypeAlias, culture) : PublishedSnapshot.Content.GetAtRoot().OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(culture); } @@ -1140,8 +1110,8 @@ namespace Umbraco.Web public static IEnumerable SiblingsAndSelf(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { - return content.Parent() != null - ? content.Parent().Children(culture) + return content.Parent != null + ? content.Parent.Children(culture) : PublishedSnapshot.Content.GetAtRoot().OfType().WhereIsInvariantOrHasCulture(culture); } diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 354c315202..efd48af2d4 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.Routing while (domainUris == null && n != null) // n is null at root { // move to parent node - n = n.Parent(); + n = n.Parent; domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, excludeDefault: false); } diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 4e9944a808..4092538481 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -87,7 +87,7 @@ namespace Umbraco.Web.Routing var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, false); while (domainUris == null && n != null) // n is null at root { - n = n.Parent(); // move to parent node + n = n.Parent; // move to parent node domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainHelper, n.Id, current, excludeDefault: true); } diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs index 1f15967996..6e768c28b6 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Web/Routing/PublishedRouter.cs @@ -276,7 +276,7 @@ namespace Umbraco.Web.Routing return true; // variant, ensure that the culture corresponding to the domain's language is published - return domainDocument.Cultures.Contains(domain.Culture.Name); + return domainDocument.Cultures.ContainsKey(domain.Culture.Name); } domains = domains.Where(IsPublishedContentDomain).ToList(); diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs index 9c6459da62..77aa3c65a1 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs @@ -171,12 +171,12 @@ namespace Umbraco.Web.Routing if (entityContent == null) continue; // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) - var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.ToArray() + var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray() ?? new[] {(string) null}; foreach (var x in entityContent.DescendantsOrSelf()) { // if this entity defines specific cultures, use those instead of the default ones - var cultures = x.Cultures.Any() ? x.Cultures : defaultCultures; + var cultures = x.Cultures.Any() ? x.Cultures.Keys : defaultCultures; foreach (var culture in cultures) { diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 27a27399bf..077680d2e2 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -188,7 +188,7 @@ namespace Umbraco.Web.Routing while (o != null) { l.Add(o.Name()); - o = o.Parent(); + o = o.Parent; } l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 06706e475b..c3a1fb128f 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.Templates if (guidUdi.EntityType == Constants.UdiEntityType.Document) newLink = urlProvider.GetUrl(guidUdi.Guid); else if (guidUdi.EntityType == Constants.UdiEntityType.Media) - newLink = mediaCache.GetById(guidUdi.Guid)?.Url(); + newLink = mediaCache.GetById(guidUdi.Guid)?.Url; if (newLink == null) newLink = "#"; @@ -171,7 +171,7 @@ namespace Umbraco.Web.Templates return match.Value; } - var url = media.Url(); + var url = media.Url; return $"{match.Groups[1].Value}{url}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; }); } From 9b36bc40978d4bb98375f18fbef5b19dd40dd686 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 7 Jun 2019 13:13:52 +1000 Subject: [PATCH 027/218] Fixes notifications when publishing fails and warnings are issues - these were not being displayed --- src/Umbraco.Web/Editors/ContentController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index de65689353..4c920626a6 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -741,7 +741,7 @@ namespace Umbraco.Web.Editors //global notifications AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); //variant specific notifications - foreach (var c in successfulCultures) + foreach (var c in successfulCultures ?? Array.Empty()) AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); } break; @@ -762,7 +762,7 @@ namespace Umbraco.Web.Editors //global notifications AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); //variant specific notifications - foreach (var c in successfulCultures) + foreach (var c in successfulCultures ?? Array.Empty()) AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); } break; @@ -1143,7 +1143,7 @@ namespace Umbraco.Web.Editors var publishStatus = Services.ContentService.SaveAndPublishBranch(contentItem.PersistedContent, force, userId: Security.CurrentUser.Id); // TODO: Deal with multiple cancellations wasCancelled = publishStatus.Any(x => x.Result == PublishResultType.FailedPublishCancelledByEvent); - successfulCultures = Array.Empty(); + successfulCultures = null; //must be null! this implies invariant return publishStatus; } From 79cd3129120456414c364499b3e03bade6a234f1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 7 Jun 2019 14:20:44 +1000 Subject: [PATCH 028/218] Fixes message when descendant publishing fails when there is a mix of variant types --- src/Umbraco.Web/Editors/ContentController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 4c920626a6..d48e586036 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -2128,7 +2128,7 @@ namespace Umbraco.Web.Editors { foreach (var c in successfulCultures) { - var names = string.Join(", ", status.Select(x => $"'{x.Content.GetCultureName(c)}'")); + var names = string.Join(", ", status.Select(x => $"'{(x.Content.ContentType.VariesByCulture() ? x.Content.GetCultureName(c) : x.Content.Name)}'")); display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedInvalid", From 14a056f4f4001ec2887eda750d482e851edbaa56 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 7 Jun 2019 11:15:58 +0200 Subject: [PATCH 029/218] Fix and cleanup --- .../PublishedContent/PublishedCultureInfos.cs | 3 +- .../PublishedContentExtensions.cs | 8 +- .../PublishedContentCache.cs | 2 +- .../PublishedMediaCache.cs | 2 +- .../XmlPublishedContent.cs | 11 +- .../Published/ConvertersTests.cs | 18 +- .../Published/NestedContentTests.cs | 68 ++---- .../PublishedContent/NuCacheChildrenTests.cs | 203 ++++++++++++++++-- .../PublishedContent/NuCacheTests.cs | 18 +- .../PublishedContentDataTableTests.cs | 62 +----- .../PublishedContent/PublishedContentTests.cs | 4 +- .../PublishedContent/PublishedMediaTests.cs | 2 +- .../SolidPublishedSnapshot.cs | 18 +- .../Routing/MediaUrlProviderTests.cs | 25 ++- src/Umbraco.Tests/Routing/UrlProviderTests.cs | 12 +- .../TestHelpers/Stubs/TestPublishedContent.cs | 66 ------ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../PublishedCache/IPublishedCache.cs | 6 +- .../PublishedCache/NuCache/ContentCache.cs | 22 +- .../PublishedCache/NuCache/MediaCache.cs | 38 +--- .../NuCache/PublishedContent.cs | 9 +- .../NuCache/PublishedSnapshotService.cs | 16 +- .../PublishedCache/PublishedCacheBase.cs | 6 +- src/Umbraco.Web/PublishedContentExtensions.cs | 4 +- 24 files changed, 322 insertions(+), 302 deletions(-) delete mode 100644 src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index eedc97dbfc..d5096158a7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -13,10 +13,9 @@ namespace Umbraco.Core.Models.PublishedContent /// public PublishedCultureInfo(string culture, string name, string urlSegment, DateTime date) { - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture)); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - Culture = culture; + Culture = culture ?? throw new ArgumentNullException(nameof(culture)); Name = name; UrlSegment = urlSegment; Date = date; diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs index a8cb0cdaf2..f220f307d6 100644 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core culture = culture ?? Current.VariationContextAccessor.VariationContext?.Culture ?? ""; // either does not vary by culture, or has the specified culture - return contents.Where(x => !ContentVariationExtensions.VariesByCulture((IPublishedContentType) x.ContentType) || HasCulture(x, culture)); + return contents.Where(x => !x.ContentType.VariesByCulture() || HasCulture(x, culture)); } /// @@ -49,7 +49,7 @@ namespace Umbraco.Core { // invariant has invariant value (whatever the requested culture) if (!content.ContentType.VariesByCulture()) - return "NAME??"; // fixme where should the invariant one come from? should Cultures contain it? + return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.Name : null; // handle context culture for variant if (culture == null) @@ -69,7 +69,7 @@ namespace Umbraco.Core { // invariant has invariant value (whatever the requested culture) if (!content.ContentType.VariesByCulture()) - return "URLSEGMENT??"; // fixme where should the invariant one come from? should Cultures contain it? + return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.UrlSegment : null; // handle context culture for variant if (culture == null) @@ -125,4 +125,4 @@ namespace Umbraco.Core : children.Where(x => x.IsInvariantOrHasCulture(culture)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 8de0209c69..8ce6b10983 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -382,7 +382,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return GetXml(preview).CreateNavigator().MoveToId(contentId.ToString(CultureInfo.InvariantCulture)); } - public override IEnumerable GetAtRoot(bool preview) + public override IEnumerable GetAtRoot(bool preview, string culture = null) { return ConvertToDocuments(GetXml(preview).SelectNodes(XPathStrings.RootDocuments), preview); } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs index ba43921f1c..999d7f040d 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs @@ -105,7 +105,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return GetUmbracoMedia(contentId) != null; } - public override IEnumerable GetAtRoot(bool preview) + public override IEnumerable GetAtRoot(bool preview, string culture = null) { var searchProvider = GetSearchProviderSafe(); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index 4a60912757..3697863cb4 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -145,8 +145,15 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } } - private static readonly Lazy> NoCultures = new Lazy>(() => new Dictionary()); - public override IReadOnlyDictionary Cultures => NoCultures.Value; + private Dictionary _cultures; + + private Dictionary GetCultures() + { + EnsureNodeInitialized(); + return new Dictionary { { "", new PublishedCultureInfo("", _name, _urlName, _updateDate) } }; + } + + public override IReadOnlyDictionary Cultures => _cultures ?? (_cultures = GetCultures()); public override string WriterName { diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 7ad81922e2..671129848c 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -4,16 +4,14 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.Components; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.PublishedCache; @@ -127,7 +125,7 @@ namespace Umbraco.Tests.Published var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", t => Enumerable.Empty()); - var cnt1 = new TestPublishedContent(cntType1, 1234, Guid.NewGuid(), new Dictionary(), false); + var cnt1 = new SolidPublishedContent(cntType1) { Id = 1234 }; cacheContent[cnt1.Id] = cnt1; Assert.AreSame(cnt1, element1.Value("prop1")); @@ -224,8 +222,16 @@ namespace Umbraco.Tests.Published var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); var element2 = new PublishedElement(elementType2, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); - var cnt1 = new TestPublishedContent(contentType1, 1003, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); - var cnt2 = new TestPublishedContent(contentType2, 1004, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); + var cnt1 = new SolidPublishedContent(contentType1) + { + Id = 1003, + Properties = new[] { new SolidPublishedProperty { Alias = "prop1", SolidHasValue = true, SolidValue = "val1" } } + }; + var cnt2 = new SolidPublishedContent(contentType1) + { + Id = 1004, + Properties = new[] { new SolidPublishedProperty { Alias = "prop2", SolidHasValue = true, SolidValue = "1003" } } + }; cacheContent[cnt1.Id] = cnt1.CreateModel(); cacheContent[cnt2.Id] = cnt2.CreateModel(); diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 432247b09f..9385b8955a 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Models; @@ -167,12 +168,16 @@ namespace Umbraco.Tests.Published var key = Guid.NewGuid(); var keyA = Guid.NewGuid(); - var content = new TestPublishedContent(contentType1, key, new[] + var content = new SolidPublishedContent(contentType1) { - new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ + Key = key, + Properties = new [] + { + new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }} ]") - }); + } + }; var value = content.Value("property1"); // nested single converter returns proper TestModel value @@ -194,13 +199,17 @@ namespace Umbraco.Tests.Published var key = Guid.NewGuid(); var keyA = Guid.NewGuid(); var keyB = Guid.NewGuid(); - var content = new TestPublishedContent(contentType2, key, new[] + var content = new SolidPublishedContent(contentType2) { - new TestPublishedProperty(contentType2.GetPropertyType("property2"), $@"[ + Key = key, + Properties = new[] + { + new TestPublishedProperty(contentType2.GetPropertyType("property2"), $@"[ {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }}, {{ ""key"": ""{keyB}"", ""propertyN1"": ""bar"", ""ncContentTypeAlias"": ""contentN1"" }} ]") - }); + } + }; var value = content.Value("property2"); // nested many converter returns proper IEnumerable value @@ -257,52 +266,5 @@ namespace Umbraco.Tests.Published public override object GetValue(string culture = null, string segment = null) => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); public override object GetXPathValue(string culture = null, string segment = null) => throw new WontImplementException(); } - - class TestPublishedContent : PublishedContentBase - { - public TestPublishedContent(IPublishedContentType contentType, Guid key, IEnumerable properties) - { - ContentType = contentType; - Key = key; - var propertiesA = properties.ToArray(); - Properties = propertiesA; - foreach (var property in propertiesA) - property.SetOwner(this); - } - - // ReSharper disable UnassignedGetOnlyAutoProperty - public override PublishedItemType ItemType { get; } - public override bool IsDraft(string culture = null) => false; - public override bool IsPublished(string culture = null) => true; - public override IPublishedContent Parent { get; } - public override IEnumerable Children { get; } - public override IEnumerable ChildrenForAllCultures => Children; - public override IPublishedContentType ContentType { get; } - // ReSharper restore UnassignedGetOnlyAutoProperty - - // ReSharper disable UnassignedGetOnlyAutoProperty - public override int Id { get; } - public override int? TemplateId { get; } - public override int SortOrder { get; } - public override string Name { get; } - public override IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public override string UrlSegment { get; } - public override string WriterName { get; } - public override string CreatorName { get; } - public override int WriterId { get; } - public override int CreatorId { get; } - public override string Path { get; } - public override DateTime CreateDate { get; } - public override DateTime UpdateDate { get; } - public override int Level { get; } - public override Guid Key { get; } - // ReSharper restore UnassignedGetOnlyAutoProperty - - public override IEnumerable Properties { get; } - public override IPublishedProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - } } } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index d77d1e4701..7e8a4b18e1 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -20,6 +20,7 @@ using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects; using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; @@ -33,20 +34,25 @@ namespace Umbraco.Tests.PublishedContent private IPublishedSnapshotService _snapshotService; private IVariationContextAccessor _variationAccesor; private IPublishedSnapshotAccessor _snapshotAccessor; - private ContentType _contentType; - private PropertyType _propertyType; + private ContentType _contentTypeInvariant; + private ContentType _contentTypeVariant; private TestDataSource _source; private void Init(IEnumerable kits) { Current.Reset(); - Current.UnlockConfigs(); - Current.Configs.Add(SettingsForTests.GenerateMockUmbracoSettings); - Current.Configs.Add(() => new GlobalSettings()); - var globalSettings = Current.Configs.Global(); - // create a data source for NuCache - _source = new TestDataSource(kits); + var factory = Mock.Of(); + Current.Factory = factory; + + var configs = new Configs(); + Mock.Get(factory).Setup(x => x.GetInstance(typeof(Configs))).Returns(configs); + var globalSettings = new GlobalSettings(); + configs.Add(SettingsForTests.GenerateMockUmbracoSettings); + configs.Add(() => globalSettings); + + var publishedModelFactory = new NoopPublishedModelFactory(); + Mock.Get(factory).Setup(x => x.GetInstance(typeof(IPublishedModelFactory))).Returns(publishedModelFactory); var runtime = Mock.Of(); Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run); @@ -59,13 +65,18 @@ namespace Umbraco.Tests.PublishedContent dataType }; - _propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Nothing }; - _contentType = new ContentType(-1) { Id = 2, Alias = "ctype", Variations = ContentVariation.Nothing }; - _contentType.AddPropertyType(_propertyType); + var propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Nothing }; + _contentTypeInvariant = new ContentType(-1) { Id = 2, Alias = "itype", Variations = ContentVariation.Nothing }; + _contentTypeInvariant.AddPropertyType(propertyType); + + propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Culture }; + _contentTypeVariant = new ContentType(-1) { Id = 3, Alias = "vtype", Variations = ContentVariation.Culture }; + _contentTypeVariant.AddPropertyType(propertyType); var contentTypes = new[] { - _contentType + _contentTypeInvariant, + _contentTypeVariant }; var contentTypeService = Mock.Of(); @@ -109,6 +120,9 @@ namespace Umbraco.Tests.PublishedContent _variationAccesor = new TestVariationContextAccessor(); _snapshotAccessor = new TestPublishedSnapshotAccessor(); + // create a data source for NuCache + _source = new TestDataSource(kits); + // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; _snapshotService = new PublishedSnapshotService(options, @@ -133,9 +147,11 @@ namespace Umbraco.Tests.PublishedContent // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); + + Mock.Get(factory).Setup(x => x.GetInstance(typeof(IVariationContextAccessor))).Returns(_variationAccesor); } - private IEnumerable GetKits() + private IEnumerable GetInvariantKits() { var paths = new Dictionary { { -1, "-1" } }; @@ -146,10 +162,11 @@ namespace Umbraco.Tests.PublishedContent var path = paths[id] = parentPath + "," + id; var level = path.Count(x => x == ','); + var now = DateTime.Now; return new ContentNodeKit { - ContentTypeId = 2, + ContentTypeId = _contentTypeInvariant.Id, Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), DraftData = null, PublishedData = new ContentData @@ -158,7 +175,7 @@ namespace Umbraco.Tests.PublishedContent Published = true, TemplateId = 0, VersionId = 1, - VersionDate = DateTime.Now, + VersionDate = now, WriterId = 0, Properties = new Dictionary(), CultureInfos = new Dictionary() @@ -184,6 +201,69 @@ namespace Umbraco.Tests.PublishedContent yield return CreateKit(12, 4, 2); } + private IEnumerable GetVariantKits() + { + var paths = new Dictionary { { -1, "-1" } }; + + Dictionary GetCultureInfos(int id, DateTime now) + { + var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 }; + + var infos = new Dictionary(); + if (en.Contains(id)) + infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false }; + if (fr.Contains(id)) + infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false }; + return infos; + } + + ContentNodeKit CreateKit(int id, int parentId, int sortOrder) + { + if (!paths.TryGetValue(parentId, out var parentPath)) + throw new Exception("Unknown parent."); + + var path = paths[id] = parentPath + "," + id; + var level = path.Count(x => x == ','); + var now = DateTime.Now; + + return new ContentNodeKit + { + ContentTypeId = _contentTypeVariant.Id, + Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData + { + Name = "N" + id, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = GetCultureInfos(id, now) + } + }; + } + + yield return CreateKit(1, -1, 1); + yield return CreateKit(2, -1, 2); + yield return CreateKit(3, -1, 3); + + yield return CreateKit(4, 1, 1); + yield return CreateKit(5, 1, 2); + yield return CreateKit(6, 1, 3); + + yield return CreateKit(7, 2, 3); + yield return CreateKit(8, 2, 2); + yield return CreateKit(9, 2, 1); + + yield return CreateKit(10, 3, 1); + + yield return CreateKit(11, 4, 1); + yield return CreateKit(12, 4, 2); + } + [Test] public void EmptyTest() { @@ -199,7 +279,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void ChildrenTest() { - Init(GetKits()); + Init(GetInvariantKits()); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -226,7 +306,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void ParentTest() { - Init(GetKits()); + Init(GetInvariantKits()); var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); _snapshotAccessor.PublishedSnapshot = snapshot; @@ -252,7 +332,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void MoveToRootTest() { - Init(GetKits()); + Init(GetInvariantKits()); // get snapshot var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); @@ -294,7 +374,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void MoveFromRootTest() { - Init(GetKits()); + Init(GetInvariantKits()); // get snapshot var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); @@ -336,7 +416,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void ReOrderTest() { - Init(GetKits()); + Init(GetInvariantKits()); // get snapshot var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); @@ -411,7 +491,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void MoveTest() { - Init(GetKits()); + Init(GetInvariantKits()); // get snapshot var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); @@ -512,11 +592,90 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id); } + [Test] + public void VariantChildrenTest() + { + Init(GetVariantKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + _variationAccesor.VariationContext = new VariationContext("en-US"); + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-en-US", "N2-en-US", "N3-en-US"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4-en-US", "N5-en-US", "N6-en-US"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9-en-US", "N8-en-US", "N7-en-US"); + + documents = snapshot.Content.GetById(3).Children().ToArray(); + AssertDocuments(documents, "N10-en-US"); + + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N11-en-US", "N12-en-US"); + + documents = snapshot.Content.GetById(10).Children().ToArray(); + AssertDocuments(documents); + + + _variationAccesor.VariationContext = new VariationContext("fr-FR"); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-fr-FR", "N3-fr-FR"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4-fr-FR", "N6-fr-FR"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9-fr-FR", "N7-fr-FR"); + + documents = snapshot.Content.GetById(3).Children().ToArray(); + AssertDocuments(documents, "N10-fr-FR"); + + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N12-fr-FR"); + + documents = snapshot.Content.GetById(10).Children().ToArray(); + AssertDocuments(documents); + + documents = snapshot.Content.GetById(1).Children("*").ToArray(); + AssertDocuments(documents, "N4-fr-FR", null, "N6-fr-FR"); + AssertDocuments("en-US", documents, "N4-en-US", "N5-en-US", "N6-en-US"); + + documents = snapshot.Content.GetById(1).Children("en-US").ToArray(); + AssertDocuments(documents, "N4-fr-FR", null, "N6-fr-FR"); + AssertDocuments("en-US", documents, "N4-en-US", "N5-en-US", "N6-en-US"); + + documents = snapshot.Content.GetById(1).ChildrenForAllCultures.ToArray(); + AssertDocuments(documents, "N4-fr-FR", null, "N6-fr-FR"); + AssertDocuments("en-US", documents, "N4-en-US", "N5-en-US", "N6-en-US"); + + + documents = snapshot.Content.GetAtRoot("*").ToArray(); + AssertDocuments(documents, "N1-fr-FR", null, "N3-fr-FR"); + + documents = snapshot.Content.GetById(1).DescendantsOrSelf().ToArray(); + AssertDocuments(documents, "N1-fr-FR", "N4-fr-FR", "N12-fr-FR", "N6-fr-FR"); + + documents = snapshot.Content.GetById(1).DescendantsOrSelf("*").ToArray(); + AssertDocuments(documents, "N1-fr-FR", "N4-fr-FR", null /*11*/, "N12-fr-FR", null /*5*/, "N6-fr-FR"); + } + private void AssertDocuments(IPublishedContent[] documents, params string[] names) { Assert.AreEqual(names.Length, documents.Length); for (var i = 0; i < names.Length; i++) - Assert.AreEqual(names[i], documents[i].Name()); + Assert.AreEqual(names[i], documents[i].Name); + } + + private void AssertDocuments(string culture, IPublishedContent[] documents, params string[] names) + { + Assert.AreEqual(names.Length, documents.Length); + for (var i = 0; i < names.Length; i++) + Assert.AreEqual(names[i], documents[i].Name(culture)); } } } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index dc035c1645..b66404c954 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -38,10 +38,18 @@ namespace Umbraco.Tests.PublishedContent private void Init() { Current.Reset(); - Current.UnlockConfigs(); - Current.Configs.Add(SettingsForTests.GenerateMockUmbracoSettings); - Current.Configs.Add(() => new GlobalSettings()); - var globalSettings = Current.Configs.Global(); + + var factory = Mock.Of(); + Current.Factory = factory; + + var configs = new Configs(); + Mock.Get(factory).Setup(x => x.GetInstance(typeof(Configs))).Returns(configs); + var globalSettings = new GlobalSettings(); + configs.Add(SettingsForTests.GenerateMockUmbracoSettings); + configs.Add(() => globalSettings); + + var publishedModelFactory = new NoopPublishedModelFactory(); + Mock.Get(factory).Setup(x => x.GetInstance(typeof(IPublishedModelFactory))).Returns(publishedModelFactory); // create a content node kit var kit = new ContentNodeKit @@ -184,6 +192,8 @@ namespace Umbraco.Tests.PublishedContent // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); + + Mock.Get(factory).Setup(x => x.GetInstance(typeof(IVariationContextAccessor))).Returns(_variationAccesor); } [Test] diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 5c22295547..cc455b8e5d 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -97,7 +97,7 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetContent(true, 1); //change a doc type alias - var c = (TestPublishedContent)doc.Children.ElementAt(0); + var c = (SolidPublishedContent)doc.Children.ElementAt(0); c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -129,7 +129,8 @@ namespace Umbraco.Tests.PublishedContent var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); var contentTypeAlias = createChildren ? "Parent" : "Child"; - var d = new TestPublishedContent + var contentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var d = new SolidPublishedContent(contentType) { CreateDate = DateTime.Now, CreatorId = 1, @@ -140,7 +141,7 @@ namespace Umbraco.Tests.PublishedContent UpdateDate = DateTime.Now, Path = "-1,3", UrlSegment = "home-page", - Name = "Page" + Guid.NewGuid().ToString(), + Name = "Page" + Guid.NewGuid(), Version = Guid.NewGuid(), WriterId = 1, WriterName = "Shannon", @@ -175,62 +176,7 @@ namespace Umbraco.Tests.PublishedContent new RawValueProperty(factory.CreatePropertyType("property3", 1), d, "value" + (indexVals + 2))); } - d.ContentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); return d; } - - // note - could probably rewrite those tests using SolidPublishedContentCache - // l8tr... - private class TestPublishedContent : IPublishedContent - { - public string Url { get; set; } - public PublishedItemType ItemType { get; set; } - public IPublishedContent Parent { get; set; } - public int Id { get; set; } - public Guid Key { get; set; } - public int? TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - public IReadOnlyDictionary Cultures => throw new NotSupportedException(); - public string UrlSegment { get; set; } - public string WriterName { get; set; } - public string CreatorName { get; set; } - public int WriterId { get; set; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - public bool IsDraft(string culture = null) => false; - public bool IsPublished(string culture = null) => true; - - public IEnumerable Properties { get; set; } - - public IEnumerable Children { get; set; } - public IEnumerable ChildrenForAllCultures => Children; - - public IPublishedProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - - public IPublishedProperty GetProperty(string alias, bool recurse) - { - var property = GetProperty(alias); - if (recurse == false) return property; - - IPublishedContent content = this; - while (content != null && (property == null || property.HasValue() == false)) - { - content = content.Parent; - property = content == null ? null : content.GetProperty(alias); - } - - return property; - } - - public IPublishedContentType ContentType { get; set; } - } } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index fd0837b0ab..f54971a197 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -821,7 +821,7 @@ namespace Umbraco.Tests.PublishedContent var level1_2 = GetNode(1175); var level1_3 = GetNode(4444); - _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root}); + _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot(It.IsAny())).Returns(new []{root}); CollectionAssertAreEqual(new []{root}, root.SiblingsAndSelf()); @@ -860,7 +860,7 @@ namespace Umbraco.Tests.PublishedContent var level1_2 = GetNode(1175); var level1_3 = GetNode(4444); - _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root}); + _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot(It.IsAny())).Returns(new []{root}); CollectionAssertAreEqual(new IPublishedContent[0], root.Siblings()); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index b789eb0ef8..f801d02c5b 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -495,7 +495,7 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(nodeId, converted.Id); Assert.AreEqual(3, converted.Level); Assert.AreEqual(1, converted.SortOrder); - Assert.AreEqual("Sam's Umbraco Image", converted.Name()); + Assert.AreEqual("Sam's Umbraco Image", converted.Name); Assert.AreEqual("-1,1111,2222,2112", converted.Path); } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index ab5a65146a..860b9b9179 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -100,7 +100,7 @@ namespace Umbraco.Tests.PublishedContent return _content.ContainsKey(contentId); } - public override IEnumerable GetAtRoot(bool preview) + public override IEnumerable GetAtRoot(bool preview, string culture = null) { return _content.Values.Where(x => x.Parent == null); } @@ -176,13 +176,19 @@ namespace Umbraco.Tests.PublishedContent #region Content + private Dictionary _cultures; + + private Dictionary GetCultures() + { + return new Dictionary { { "", new PublishedCultureInfo("", Name, UrlSegment, UpdateDate) } }; + } + public int Id { get; set; } public Guid Key { get; set; } public int? TemplateId { get; set; } public int SortOrder { get; set; } public string Name { get; set; } - public PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException(); - public IReadOnlyDictionary Cultures => throw new NotSupportedException(); + public IReadOnlyDictionary Cultures => _cultures ?? (_cultures = GetCultures()); public string UrlSegment { get; set; } public string WriterName { get; set; } public string CreatorName { get; set; } @@ -195,7 +201,7 @@ namespace Umbraco.Tests.PublishedContent public int Level { get; set; } public string Url { get; set; } - public PublishedItemType ItemType { get { return PublishedItemType.Content; } } + public PublishedItemType ItemType => PublishedItemType.Content; public bool IsDraft(string culture = null) => false; public bool IsPublished(string culture = null) => true; @@ -214,7 +220,7 @@ namespace Umbraco.Tests.PublishedContent #region ContentType - public IPublishedContentType ContentType { get; private set; } + public IPublishedContentType ContentType { get; set; } #endregion @@ -236,7 +242,7 @@ namespace Umbraco.Tests.PublishedContent while (content != null && (property == null || property.HasValue() == false)) { content = content.Parent; - property = content == null ? null : content.GetProperty(alias); + property = content?.GetProperty(alias); } return property; diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 9eee6eb32d..5af48e64b1 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Moq; using Newtonsoft.Json; @@ -11,7 +10,6 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Web.Routing; @@ -56,10 +54,10 @@ namespace Umbraco.Tests.Routing const string expected = "/media/rfeiw584/test.jpg"; var configuration = new ImageCropperConfiguration(); - var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue + var imageCropperValue = new ImageCropperValue { Src = expected - }); + }; var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); @@ -120,15 +118,28 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(daMediaUrl, resolvedUrl); } - private static TestPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration) + private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration) { var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new[] {umbracoFilePropertyType}, ContentVariation.Nothing); - return new TestPublishedContent(contentType, 1234, Guid.NewGuid(), - new Dictionary {{"umbracoFile", propertyValue } }, false); + return new SolidPublishedContent(contentType) + { + Id = 1234, + Key = Guid.NewGuid(), + Properties = new[] + { + new SolidPublishedProperty + { + Alias = "umbracoFile", + SolidValue = propertyValue, + SolidHasValue = true, + PropertyType = umbracoFilePropertyType + } + } + }; } private static PublishedPropertyType CreatePropertyType(string propertyEditorAlias, object dataTypeConfiguration, ContentVariation variation) diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index ca13e06f0a..02aa95cd2e 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -10,8 +10,8 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.LegacyXmlPublishedCache; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -142,7 +142,7 @@ namespace Umbraco.Tests.Routing new DefaultUrlProvider(umbracoSettings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings: globalSettings.Object); - + var result = umbracoContext.UrlProvider.GetUrl(nodeId); Assert.AreEqual(niceUrlMatch, result); } @@ -159,7 +159,7 @@ namespace Umbraco.Tests.Routing var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); + var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) @@ -204,7 +204,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); + var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) @@ -258,7 +258,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); - var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); + var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); publishedContentCache.Setup(x => x.GetRouteById(1234, "fr-FR")) @@ -332,7 +332,7 @@ namespace Umbraco.Tests.Routing }, globalSettings: globalSettings.Object); //mock the Umbraco settings that we need - + Assert.AreEqual("#", umbracoContext.UrlProvider.GetUrl(999999)); umbracoContext.UrlProvider.Mode = UrlMode.Absolute; diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs deleted file mode 100644 index 62f93d106f..0000000000 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.PublishedCache; - -namespace Umbraco.Tests.TestHelpers.Stubs -{ - internal class TestPublishedContent : PublishedElement, IPublishedContent - { - public TestPublishedContent(IPublishedContentType contentType, int id, Guid key, Dictionary values, bool previewing, Dictionary cultures = null) - : base(contentType, key, values, previewing) - { - Id = id; - Cultures = cultures ?? new Dictionary(); - } - - public int Id { get; } - public int? TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - public IVariationContextAccessor VariationContextAccessor { get; set; } - public IReadOnlyDictionary Cultures { get; set; } - public string UrlSegment { get; set; } - public string DocumentTypeAlias => ContentType.Alias; - public int DocumentTypeId { get; set; } - public string WriterName { get; set; } - public string CreatorName { get; set; } - public int WriterId { get; set; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - public string Url { get; set; } - public PublishedItemType ItemType => ContentType.ItemType; - public bool IsDraft(string culture = null) => false; - public bool IsPublished(string culture = null) => true; - public IPublishedContent Parent { get; set; } - public IEnumerable Children { get; set; } - public IEnumerable ChildrenForAllCultures => Children; - - // copied from PublishedContentBase - public IPublishedProperty GetProperty(string alias, bool recurse) - { - var property = GetProperty(alias); - if (recurse == false) return property; - - IPublishedContent content = this; - var firstNonNullProperty = property; - while (content != null && (property == null || property.HasValue() == false)) - { - content = content.Parent; - property = content?.GetProperty(alias); - if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; - } - - // if we find a content with the property with a value, return that property - // if we find no content with the property, return null - // if we find a content with the property without a value, return that property - // have to save that first property while we look further up, hence firstNonNullProperty - - return property != null && property.HasValue() ? property : firstNonNullProperty; - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index cd3155c7c3..4f4a83dc26 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -206,7 +206,6 @@ - diff --git a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs index 33094221f7..0370088f77 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs @@ -84,16 +84,18 @@ namespace Umbraco.Web.PublishedCache /// Gets contents at root. /// /// A value indicating whether to consider unpublished content. + /// A culture. /// The contents. /// The value of overrides defaults. - IEnumerable GetAtRoot(bool preview); + IEnumerable GetAtRoot(bool preview, string culture = null); /// /// Gets contents at root. /// + /// A culture. /// The contents. /// Considers published or unpublished content depending on defaults. - IEnumerable GetAtRoot(); + IEnumerable GetAtRoot(string culture = null); /// /// Gets a content resulting from an XPath query. diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 4dcee5eb24..fa879b7879 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -1,20 +1,15 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Core.Xml.XPath; using Umbraco.Web.PublishedCache.NuCache.Navigable; -using Umbraco.Web.Routing; namespace Umbraco.Web.PublishedCache.NuCache { @@ -25,6 +20,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IAppCache _elementsCache; private readonly IDomainCache _domainCache; private readonly IGlobalSettings _globalSettings; + private readonly IVariationContextAccessor _variationContextAccessor; #region Constructor @@ -33,7 +29,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // it's too late for UmbracoContext which has captured previewDefault and stuff into these ctor vars // but, no, UmbracoContext returns snapshot.Content which comes from elements SO a resync should create a new cache - public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, IAppCache elementsCache, IDomainCache domainCache, IGlobalSettings globalSettings) + public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, IAppCache elementsCache, IDomainCache domainCache, IGlobalSettings globalSettings, IVariationContextAccessor variationContextAccessor) : base(previewDefault) { _snapshot = snapshot; @@ -41,6 +37,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _elementsCache = elementsCache; _domainCache = domainCache; _globalSettings = globalSettings; + _variationContextAccessor = variationContextAccessor; } private bool HideTopLevelNodeFromPath => _globalSettings.HideTopLevelNodeFromPath; @@ -241,7 +238,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var guidUdi = contentId as GuidUdi; if (guidUdi == null) throw new ArgumentException($"Udi must be of type {typeof(GuidUdi).Name}.", nameof(contentId)); - + if (guidUdi.EntityType != Constants.UdiEntityType.Document) throw new ArgumentException($"Udi entity type must be \"{Constants.UdiEntityType.Document}\".", nameof(contentId)); @@ -256,11 +253,18 @@ namespace Umbraco.Web.PublishedCache.NuCache return preview || n.PublishedModel != null; } - public override IEnumerable GetAtRoot(bool preview) + IEnumerable INavigableData.GetAtRoot(bool preview) => GetAtRoot(preview); + + public override IEnumerable GetAtRoot(bool preview, string culture = null) { + // handle context culture for variant + if (culture == null) + culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; + // both .Draft and .Published cannot be null at the same time // root is already sorted by sortOrder, and does not contain nulls - return _snapshot.GetAtRoot().Select(n => GetNodePublishedContent(n, preview)); + var atRoot = _snapshot.GetAtRoot().Select(n => GetNodePublishedContent(n, preview)); + return culture == "*" ? atRoot : atRoot.Where(x => x.IsInvariantOrHasCulture(culture)); } private static IPublishedContent GetNodePublishedContent(ContentNode node, bool preview) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs index fc04d9d1b3..182086ed7f 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs @@ -14,17 +14,15 @@ namespace Umbraco.Web.PublishedCache.NuCache internal class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableData, IDisposable { private readonly ContentStore.Snapshot _snapshot; - private readonly IAppCache _snapshotCache; - private readonly IAppCache _elementsCache; + private readonly IVariationContextAccessor _variationContextAccessor; #region Constructors - public MediaCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, IAppCache elementsCache) + public MediaCache(bool previewDefault, ContentStore.Snapshot snapshot, IVariationContextAccessor variationContextAccessor) : base(previewDefault) { _snapshot = snapshot; - _snapshotCache = snapshotCache; - _elementsCache = elementsCache; + _variationContextAccessor = variationContextAccessor; } #endregion @@ -65,30 +63,16 @@ namespace Umbraco.Web.PublishedCache.NuCache return n != null; } - public override IEnumerable GetAtRoot(bool preview) + IEnumerable INavigableData.GetAtRoot(bool preview) => GetAtRoot(preview); + + public override IEnumerable GetAtRoot(bool preview, string culture = null) { - if (PublishedSnapshotService.CacheContentCacheRoots == false) - return GetAtRootNoCache(); + // handle context culture for variant + if (culture == null) + culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; - var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing - ? _elementsCache - : _snapshotCache; - - if (cache == null) - return GetAtRootNoCache(); - - // note: ToArray is important here, we want to cache the result, not the function! - return (IEnumerable)cache.Get( - CacheKeys.MediaCacheRoots(false), // ignore preview, only 1 key! - () => GetAtRootNoCache().ToArray()); - } - - private IEnumerable GetAtRootNoCache() - { - var c = _snapshot.GetAtRoot(); - - // ignore preview, there's only draft for media - return c.Select(n => n.PublishedModel); + var atRoot = _snapshot.GetAtRoot().Select(x => x.PublishedModel); + return culture == "*" ? atRoot : atRoot.Where(x => x.IsInvariantOrHasCulture(culture)); } public override bool HasContent(bool preview) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 67f219be21..1995e8b424 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -186,11 +186,14 @@ namespace Umbraco.Web.PublishedCache.NuCache { get { - if (!ContentType.VariesByCulture()) - return EmptyCultures; - if (_cultures != null) return _cultures; + if (!ContentType.VariesByCulture()) + return _cultures = new Dictionary + { + { "", new PublishedCultureInfo("", ContentData.Name, _urlSegment, CreateDate) } + }; + if (ContentData.CultureInfos == null) throw new Exception("panic: _contentDate.CultureInfos is null."); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 5a3672ed57..7a7682a797 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -63,18 +63,6 @@ namespace Umbraco.Web.PublishedCache.NuCache // so making it configurable. public static readonly bool FullCacheWhenPreviewing = true; - // define constant - determines whether to cache the published content - // objects (in the elements cache, or snapshot cache, depending on preview) - // or to re-fetch them all the time. caching is faster but uses more - // memory. not sure what we want. - public static readonly bool CachePublishedContentChildren = true; - - // define constant - determines whether to cache the content cache root - // objects (in the elements cache, or snapshot cache, depending on preview) - // or to re-fetch them all the time. caching is faster but uses more - // memory - not sure what we want. - public static readonly bool CacheContentCacheRoots = true; - #region Constructors //private static int _singletonCheck; @@ -1070,8 +1058,8 @@ namespace Umbraco.Web.PublishedCache.NuCache return new PublishedSnapshot.PublishedSnapshotElements { - ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainCache, _globalSettings), - MediaCache = new MediaCache(previewDefault, mediaSnap, snapshotCache, elementsCache), + ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainCache, _globalSettings, VariationContextAccessor), + MediaCache = new MediaCache(previewDefault, mediaSnap, VariationContextAccessor), MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor, _entitySerializer), DomainCache = domainCache, SnapshotCache = snapshotCache, diff --git a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs index d726664db0..1f637663e5 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs @@ -37,11 +37,11 @@ namespace Umbraco.Web.PublishedCache public bool HasById(int contentId) => HasById(PreviewDefault, contentId); - public abstract IEnumerable GetAtRoot(bool preview); + public abstract IEnumerable GetAtRoot(bool preview, string culture = null); - public IEnumerable GetAtRoot() + public IEnumerable GetAtRoot(string culture = null) { - return GetAtRoot(PreviewDefault); + return GetAtRoot(PreviewDefault, culture); } public abstract IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index fbfc52f4d8..d7283a1e90 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -833,7 +833,7 @@ namespace Umbraco.Web if (content == null) throw new ArgumentNullException(nameof(content)); if (orSelf) yield return content; - foreach (var desc in content.Children(culture).SelectMany(x => x.EnumerateDescendants())) + foreach (var desc in content.Children(culture).SelectMany(x => x.EnumerateDescendants(culture))) yield return desc; } @@ -841,7 +841,7 @@ namespace Umbraco.Web { yield return content; - foreach (var desc in content.Children(culture).SelectMany(x => x.EnumerateDescendants())) + foreach (var desc in content.Children(culture).SelectMany(x => x.EnumerateDescendants(culture))) yield return desc; } From 120f4c30963b53aee6955470cff5137672d57ae3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2019 16:31:32 +1000 Subject: [PATCH 030/218] removes scope.busy callls (legacy old stuff), add ability to preserve editor state between routes, for content we now preserve editor state when redirecting to new content and then rebind some values from that editor state when the editor loads, updates editorstate.service and filemanager.service to manage themselves on route change (decoupling), fixes variant mapper to ensure that the variants are always returned in a deterministic order. --- .../components/content/edit.controller.js | 141 ++++++++++-------- .../services/contenteditinghelper.service.js | 41 ++--- .../common/services/editorstate.service.js | 41 ++++- .../common/services/filemanager.service.js | 25 +++- src/Umbraco.Web.UI.Client/src/init.js | 11 +- .../Models/Mapping/ContentVariantMapper.cs | 2 +- 6 files changed, 155 insertions(+), 106 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 3a874f83c6..69cde0ba7d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -26,26 +26,26 @@ $scope.allowOpen = true; $scope.app = null; - function init() { - + function init(firstLoad) { + var content = $scope.content; - + // we need to check wether an app is present in the current data, if not we will present the default app. var isAppPresent = false; - + // on first init, we dont have any apps. but if we are re-initializing, we do, but ... if ($scope.app) { - + // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) - _.forEach(content.apps, function(app) { + _.forEach(content.apps, function (app) { if (app === $scope.app) { isAppPresent = true; } }); - + // if we did reload our DocType, but still have the same app we will try to find it by the alias. if (isAppPresent === false) { - _.forEach(content.apps, function(app) { + _.forEach(content.apps, function (app) { if (app.alias === $scope.app.alias) { isAppPresent = true; app.active = true; @@ -53,7 +53,7 @@ } }); } - + } // if we still dont have a app, lets show the first one: @@ -61,7 +61,23 @@ content.apps[0].active = true; $scope.appChanged(content.apps[0]); } - + + if (firstLoad) { + + //Check if the editor state is holding a reference to a new item and merge it's notifications. + //We need to do this when creating new content because when it's created, we redirect to a new route and re-load the content from the server + //but we need to maintain it's 'notifications' data so we can re-bind it in the UI. + //The only time that editorState will contain something here is if it was preserved and that is only explicitly done when creating new content and redirecting. + + var currState = editorState.getCurrent(); + if (currState && currState.udi === content.udi) { + content.notifications = currState.notifications; + for (var i = 0; i < content.variants.length; i++) { + content.variants[i].notifications = currState.variants[i].notifications; + } + } + } + editorState.set(content); //We fetch all ancestors of the node to generate the footer breadcrumb navigation @@ -118,10 +134,10 @@ function isContentCultureVariant() { return $scope.content.variants.length > 1; } - + function reload() { $scope.page.loading = true; - loadContent().then(function() { + loadContent().then(function () { $scope.page.loading = false; }); } @@ -134,7 +150,7 @@ evts.push(eventsService.on("editors.documentType.saved", function (name, args) { // if this content item uses the updated doc type we need to reload the content item - if(args && args.documentType && $scope.content.documentType.id === args.documentType.id) { + if (args && args.documentType && $scope.content.documentType.id === args.documentType.id) { reload(); } })); @@ -144,7 +160,7 @@ /** * This does the content loading and initializes everything, called on first load */ - function loadContent() { + function loadContent(firstLoad) { //we are editing so get the content item from the server return $scope.getMethod()($scope.contentId) @@ -158,7 +174,7 @@ "/content/content/edit/" + data.parentId; } - init(); + init(firstLoad); syncTreeNode($scope.content, $scope.content.path, true); @@ -219,7 +235,7 @@ $scope.page.showPreviewButton = true; } - + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ function syncTreeNode(content, path, initialLoad) { @@ -328,7 +344,7 @@ $scope.contentForm.$dirty = false; for (var i = 0; i < $scope.content.variants.length; i++) { - if($scope.content.variants[i].isDirty){ + if ($scope.content.variants[i].isDirty) { $scope.contentForm.$dirty = true; return; } @@ -337,7 +353,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid var fieldsToRollback = checkValidility(); @@ -364,11 +380,6 @@ function (err) { syncTreeNode($scope.content, $scope.content.path); - //error - if (err) { - editorState.set($scope.content); - } - resetNestedFieldValiation(fieldsToRollback); return $q.reject(err); @@ -421,9 +432,9 @@ //need to show a notification else it's not clear there was an error. localizationService.localizeMany([ - "speechBubbles_validationFailedHeader", - "speechBubbles_validationFailedMessage" - ] + "speechBubbles_validationFailedHeader", + "speechBubbles_validationFailedMessage" + ] ).then(function (data) { notificationsService.error(data[0], data[1]); }); @@ -453,7 +464,7 @@ $scope.page.loading = true; - loadContent().then(function () { + loadContent(true).then(function () { $scope.page.loading = false; }); } @@ -510,7 +521,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveToPublish", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -518,14 +529,14 @@ saveMethod: contentResource.sendToPublish, action: "sendToPublish", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -534,7 +545,7 @@ return $q.when(err); }); }, - close: function() { + close: function () { overlayService.close(); } }; @@ -570,7 +581,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_saveAndPublish", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -578,14 +589,14 @@ saveMethod: contentResource.publish, action: "publish", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -594,7 +605,7 @@ return $q.when(err); }); }, - close: function() { + close: function () { overlayService.close(); } }; @@ -634,7 +645,7 @@ variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation submitButtonLabelKey: "buttons_save", - submit: function(model) { + submit: function (model) { model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response @@ -642,14 +653,14 @@ saveMethod: $scope.saveMethod(), action: "save", showNotifications: false - }).then(function(data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function(err) { + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { clearDirtyState($scope.content.variants); model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties @@ -658,7 +669,7 @@ return $q.when(err); }); }, - close: function(oldModel) { + close: function (oldModel) { overlayService.close(); } }; @@ -685,7 +696,7 @@ }; - $scope.schedule = function() { + $scope.schedule = function () { clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "schedule" })) { @@ -755,7 +766,7 @@ } }; - $scope.publishDescendants = function() { + $scope.publishDescendants = function () { clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { @@ -883,13 +894,13 @@ * @param {any} app */ $scope.appChanged = function (app) { - + $scope.app = app; - + $scope.$broadcast("editors.apps.appChanged", { app: app }); - + createButtons($scope.content); - + }; /** @@ -907,11 +918,11 @@ $scope.infiniteModel.close($scope.infiniteModel); } }; - + /** * Call back when user click the back-icon */ - $scope.onBack = function() { + $scope.onBack = function () { if ($scope.infiniteModel && $scope.infiniteModel.close) { $scope.infiniteModel.close($scope.infiniteModel); } else { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 1b2be5f635..f54accbba0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -5,7 +5,7 @@ * @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by * all editors to share logic and reduce the amount of replicated code among editors. **/ -function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) { +function contentEditingHelper(fileManager, $q, $location, $routeParams, editorState, notificationsService, navigationService, localizationService, serverValidationManager, formHelper) { function isValidIdentifier(id) { @@ -35,8 +35,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica return { /** Used by the content editor and mini content editor to perform saving operations */ - // TODO: Make this a more helpful/reusable method for other form operations! we can simplify this form most forms - // = this is already done in the formhelper service contentEditorPerformSave: function (args) { if (!angular.isObject(args)) { throw "args must be an object"; @@ -62,9 +60,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //we will use the default one for content if not specified var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; - if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, action: args.action })) { - - args.scope.busy = true; + if (formHelper.submitForm({ scope: args.scope, action: args.action })) { return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications) .then(function (data) { @@ -80,7 +76,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } }); - args.scope.busy = false; + //update editor state to what is current + editorState.set(args.content); + return $q.resolve(data); }, function (err) { @@ -93,7 +91,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } }); - args.scope.busy = false; + //update editor state to what is current + editorState.set(args.content); + return $q.reject(err); }); } @@ -265,7 +265,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // if publishing is allowed also allow schedule publish // we add this manually becuase it doesn't have a permission so it wont // get picked up by the loop through permissions - if( _.contains(args.content.allowedActions, "U")) { + if (_.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); buttons.subButtons.push(createButtonDefinition("PUBLISH_DESCENDANTS")); } @@ -274,7 +274,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // so long as it's already published and if the user has access to publish // and the user has access to unpublish (may have been removed via Event) if (!args.create) { - var hasPublishedVariant = args.content.variants.filter(function(variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; + var hasPublishedVariant = args.content.variants.filter(function (variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0; if (hasPublishedVariant && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) { buttons.subButtons.push(createButtonDefinition("Z")); } @@ -444,7 +444,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var shouldIgnore = function (propName) { return _.some([ "variants", - + "tabs", "properties", "apps", @@ -600,16 +600,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica } } + // we need to detect what properties have changed and re-bind them with the server data. + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. Then we need - // to re-bind any server validation errors after the digest takes place. - - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { - args.rebindCallback(); - } - - //notify all validators (don't clear the server validations though since we need to maintain their state because of + // we are not redirecting because this is not new content, it is existing content. In this case + // notify all validators (don't clear the server validations though since we need to maintain their state because of // how the variant switcher works in content). server validation state is always cleared when an editor first loads // and in theory when an editor is destroyed. serverValidationManager.notify(); @@ -680,6 +678,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //clear the query strings navigationService.clearSearch(["cculture"]); + //preserve the editor state when we are redirecting to new content + editorState.preserve(); + //change to new path $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index 5e42af9c5e..acfef46e75 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -5,13 +5,16 @@ * * @description * Tracks the parent object for complex editors by exposing it as - * an object reference via editorState.current.entity + * an object reference via editorState.current.getCurrent(). + * The state is cleared on each successful route. * * it is possible to modify this object, so should be used with care */ -angular.module('umbraco.services').factory("editorState", function() { +angular.module('umbraco.services').factory("editorState", function ($rootScope) { var current = null; + var preserveBetweenRoute = false; + var state = { /** @@ -40,7 +43,7 @@ angular.module('umbraco.services').factory("editorState", function() { * Since the editorstate entity is read-only, you cannot set it to null * only through the reset() method */ - reset: function() { + reset: function () { current = null; }, @@ -59,8 +62,25 @@ angular.module('umbraco.services').factory("editorState", function() { * editorState.current can not be overwritten, you should only read values from it * since modifying individual properties should be handled by the property editors */ - getCurrent: function() { + getCurrent: function () { return current; + }, + + /** + * @ngdoc function + * @name umbraco.services.angularHelper#preserve + * @methodOf umbraco.services.editorState + * @function + * + * @description + * When called it will flag the state to be preserved after the next route. + * Normally the editorState is cleared on each successful route but in some cases it should be preserved so calling this will preserve it. + * + * editorState.current can not be overwritten, you should only read values from it + * since modifying individual properties should be handled by the property editors + */ + preserve: function () { + preserveBetweenRoute = true; } }; @@ -76,5 +96,18 @@ angular.module('umbraco.services').factory("editorState", function() { } }); + //execute on each successful route (this is only bound once per application since a service is a singleton) + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + + if (!preserveBetweenRoute) { + //reset the editorState on each successful route chage + state.reset(); + } + + //always reset this + preserveBetweenRoute = false; + + }); + return state; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 6d319ad90a..8fe6761c94 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -8,11 +8,12 @@ * that need to attach files. * When a route changes successfully, we ensure that the collection is cleared. */ -function fileManager() { +function fileManager($rootScope) { var fileCollection = []; - return { + + var mgr = { /** * @ngdoc function * @name umbraco.services.fileManager#addFiles @@ -24,7 +25,7 @@ function fileManager() { * for the files collection that effectively clears the files for the specified editor. */ setFiles: function (args) { - + //propertyAlias, files if (!angular.isString(args.propertyAlias)) { throw "args.propertyAlias must be a non empty string"; @@ -52,7 +53,7 @@ function fileManager() { fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData }); } }, - + /** * @ngdoc function * @name umbraco.services.fileManager#getFiles @@ -62,10 +63,10 @@ function fileManager() { * @description * Returns all of the files attached to the file manager */ - getFiles: function() { + getFiles: function () { return fileCollection; }, - + /** * @ngdoc function * @name umbraco.services.fileManager#clearFiles @@ -78,7 +79,17 @@ function fileManager() { clearFiles: function () { fileCollection = []; } -}; + }; + + //execute on each successful route (this is only bound once per application since a service is a singleton) + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + //reset the file manager on each route change, the file collection is only relavent + // when working in an editor and submitting data to the server. + //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files. + mgr.clearFiles(); + }); + + return mgr; } angular.module('umbraco.services').factory('fileManager', fileManager); diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index eaa2fe7b31..2e3aeb7e8d 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,6 +1,6 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource', - function (userService, $q, $log, $rootScope, $route, $location, urlHelper, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) { +app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', + function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) { //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so @@ -91,13 +91,6 @@ app.run(['userService', '$q', '$log', '$rootScope', '$route', '$location', 'urlH $rootScope.locationTitle = "Umbraco - " + $location.$$host; } - //reset the editorState on each successful route chage - editorState.reset(); - - //reset the file manager on each route change, the file collection is only relavent - // when working in an editor and submitting data to the server. - //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files. - fileManager.clearFiles(); }); /** When the route change is rejected - based on checkAuth - we'll prevent the rejected route from executing including diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index 560d398a2c..3450de5dc5 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Models.Mapping variants.Remove(defaultLang); //Sort the remaining languages a-z - variants = variants.OrderBy(x => x.Name).ToList(); + variants = variants.OrderBy(x => x.Language.IsoCode).ToList(); //Insert the default language as the first item variants.Insert(0, defaultLang); From 1f64ae468257b39310bddfb470389b418eb1d402 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2019 17:17:19 +1000 Subject: [PATCH 031/218] Fixes language/variant mapping so it's consistent and by lang name not iso --- src/Umbraco.Web/Editors/LanguageController.cs | 2 +- src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/LanguageController.cs b/src/Umbraco.Web/Editors/LanguageController.cs index 2ee77ca418..650dcea6e9 100644 --- a/src/Umbraco.Web/Editors/LanguageController.cs +++ b/src/Umbraco.Web/Editors/LanguageController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Editors { var allLanguages = Services.LocalizationService.GetAllLanguages(); - return Mapper.MapEnumerable(allLanguages); + return Mapper.Map, IEnumerable>(allLanguages); } [HttpGet] diff --git a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs index 3450de5dc5..c279ae2c70 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Models.Mapping variants.Remove(defaultLang); //Sort the remaining languages a-z - variants = variants.OrderBy(x => x.Language.IsoCode).ToList(); + variants = variants.OrderBy(x => x.Language.Name).ToList(); //Insert the default language as the first item variants.Insert(0, defaultLang); From e6a8ba84aa0424526c3c82b653e494817511ba2e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2019 21:47:45 +1000 Subject: [PATCH 032/218] starts re-binding the variant dialogs on create for the content editor (WIP - will probably revert and try a different approach) --- .../components/content/edit.controller.js | 204 ++++++++++-------- .../src/common/services/overlay.service.js | 19 +- 2 files changed, 132 insertions(+), 91 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 69cde0ba7d..38dce4985b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -26,7 +26,37 @@ $scope.allowOpen = true; $scope.app = null; - function init(firstLoad) { + //called only one time when the editor first loads + function firstLoad(content) { + //Check if the editor state is holding a reference to a new item and merge it's notifications. + //We need to do this when creating new content because when it's created, we redirect to a new route and re-load the content from the server + //but we need to maintain it's 'notifications' data so we can re-bind it in the UI. + //The only time that editorState will contain something here is if it was preserved and that is only explicitly done when creating new content and redirecting. + + var currState = editorState.getCurrent(); + if (currState && currState.udi === content.udi) { + content.notifications = currState.notifications; + for (var i = 0; i < content.variants.length; i++) { + content.variants[i].notifications = currState.variants[i].notifications; + } + + //Now check if the overlay service has an active overlay open that was owned by this editor prior to redirecting. + //This will occur if new variant content is created and there were notifications/errors for variants, the variant dialog will remain open + //but it will need to be refreshed with the new editor data since it will be holding a reference to stale scopes. + var currOverlay = overlayService.getCurrent(); + if (currOverlay && currOverlay.view.startsWith("views/content/overlays")) { + switch (currOverlay.view) { + case "views/content/overlays/publish.html": + overlayService.refresh(saveAndPublishDialog()); + break; + } + } + + } + } + + //this initializes the editor with the data which will be called more than once if the data is re-loaded + function init(isFirstLoad) { var content = $scope.content; @@ -62,20 +92,8 @@ $scope.appChanged(content.apps[0]); } - if (firstLoad) { - - //Check if the editor state is holding a reference to a new item and merge it's notifications. - //We need to do this when creating new content because when it's created, we redirect to a new route and re-load the content from the server - //but we need to maintain it's 'notifications' data so we can re-bind it in the UI. - //The only time that editorState will contain something here is if it was preserved and that is only explicitly done when creating new content and redirecting. - - var currState = editorState.getCurrent(); - if (currState && currState.udi === content.udi) { - content.notifications = currState.notifications; - for (var i = 0; i < content.variants.length; i++) { - content.variants[i].notifications = currState.variants[i].notifications; - } - } + if (isFirstLoad) { + firstLoad(content); } editorState.set(content); @@ -160,7 +178,7 @@ /** * This does the content loading and initializes everything, called on first load */ - function loadContent(firstLoad) { + function loadContent(isFirstLoad) { //we are editing so get the content item from the server return $scope.getMethod()($scope.contentId) @@ -174,7 +192,7 @@ "/content/content/edit/" + data.parentId; } - init(firstLoad); + init(isFirstLoad); syncTreeNode($scope.content, $scope.content.path, true); @@ -440,6 +458,83 @@ }); } + /** returns the save and publish dialog model */ + function saveAndPublishDialog() { + var dialog = { + parentScope: $scope, + view: "views/content/overlays/publish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "buttons_saveAndPublish", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + //we need to return this promise so that the dialog can handle the result and wire up the validation response + return performSave({ + saveMethod: contentResource.publish, + action: "publish", + showNotifications: false + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); + }, + close: function () { + overlayService.close(); + } + }; + return dialog; + } + + /** Returns the unpublish dialog model */ + function unpublishDialog() { + var dialog = { + parentScope: $scope, + view: "views/content/overlays/unpublish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "content_unpublish", + submitButtonStyle: "warning", + submit: function (model) { + + model.submitButtonState = "busy"; + + var selectedVariants = _.filter(model.variants, v => v.save && v.language); //ignore invariant + var culturesForUnpublishing = _.map(selectedVariants, v => v.language.culture); + + contentResource.unpublish($scope.content.id, culturesForUnpublishing) + .then(function (data) { + formHelper.resetForm({ scope: $scope }); + contentEditingHelper.reBindChangedProperties($scope.content, data); + init(); + syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = "success"; + eventsService.emit("content.unpublished", { content: $scope.content }); + overlayService.close(); + }, function (err) { + $scope.page.buttonGroupState = 'error'; + }); + + + }, + close: function () { + overlayService.close(); + } + }; + return dialog; + } + if ($scope.page.isNew) { $scope.page.loading = true; @@ -471,41 +566,8 @@ $scope.unpublish = function () { clearNotifications($scope.content); - if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { - var dialog = { - parentScope: $scope, - view: "views/content/overlays/unpublish.html", - variants: $scope.content.variants, //set a model property for the dialog - skipFormValidation: true, //when submitting the overlay form, skip any client side validation - submitButtonLabelKey: "content_unpublish", - submitButtonStyle: "warning", - submit: function (model) { - - model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, v => v.save && v.language); //ignore invariant - var culturesForUnpublishing = _.map(selectedVariants, v => v.language.culture); - - contentResource.unpublish($scope.content.id, culturesForUnpublishing) - .then(function (data) { - formHelper.resetForm({ scope: $scope }); - contentEditingHelper.reBindChangedProperties($scope.content, data); - init(); - syncTreeNode($scope.content, data.path); - $scope.page.buttonGroupState = "success"; - eventsService.emit("content.unpublished", { content: $scope.content }); - overlayService.close(); - }, function (err) { - $scope.page.buttonGroupState = 'error'; - }); - - - }, - close: function () { - overlayService.close(); - } - }; - overlayService.open(dialog); + if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { + overlayService.open(unpublishDialog()); } }; @@ -574,43 +636,7 @@ if (isContentCultureVariant()) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { - - var dialog = { - parentScope: $scope, - view: "views/content/overlays/publish.html", - variants: $scope.content.variants, //set a model property for the dialog - skipFormValidation: true, //when submitting the overlay form, skip any client side validation - submitButtonLabelKey: "buttons_saveAndPublish", - submit: function (model) { - model.submitButtonState = "busy"; - clearNotifications($scope.content); - //we need to return this promise so that the dialog can handle the result and wire up the validation response - return performSave({ - saveMethod: contentResource.publish, - action: "publish", - showNotifications: false - }).then(function (data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function (err) { - clearDirtyState($scope.content.variants); - model.submitButtonState = "error"; - //re-map the dialog model since we've re-bound the properties - dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); - }); - }, - close: function () { - overlayService.close(); - } - }; - - overlayService.open(dialog); + overlayService.open(saveAndPublishDialog()); } else { showValidationNotification(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index e853e07092..e6564566a8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -12,6 +12,18 @@ var currentOverlay = null; + function getCurrent() { + return currentOverlay; + } + + function refresh(overlay) { + //extend the passed overlay on top of the current overlay and then re-assign to currentOverlay + var merged = angular.extend({}, currentOverlay, overlay); + currentOverlay = merged; + + eventsService.emit("appState.overlay", currentOverlay); + } + function open(newOverlay) { // prevent two open overlays at the same time @@ -45,7 +57,7 @@ overlay.show = true; backdropService.open(backdropOptions); currentOverlay = overlay; - eventsService.emit("appState.overlay", overlay); + eventsService.emit("appState.overlay", currentOverlay); } function close() { @@ -68,7 +80,9 @@ var service = { open: open, close: close, - ysod: ysod + ysod: ysod, + refresh: refresh, + getCurrent: getCurrent }; return service; @@ -77,4 +91,5 @@ angular.module("umbraco.services").factory("overlayService", overlayService); + })(); From d8180604e87d6e960eac425f1ebd308b0c0d5311 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Jun 2019 22:10:53 +1000 Subject: [PATCH 033/218] fixes SimilarNodeName --- .../Persistence/Repositories/Implement/SimilarNodeName.cs | 2 +- .../Persistence/Repositories/SimilarNodeNameTests.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs index 9f27b6b9e3..99e824757d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/SimilarNodeName.cs @@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (uniqueing) { - if (name.NumPos > 0 && name.Name.StartsWith(nodeName) && name.NumVal == uniqueNumber) + if (name.NumPos > 0 && name.Name.InvariantStartsWith(nodeName) && name.NumVal == uniqueNumber) uniqueNumber++; else break; diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index 3c23223c9f..961496ae16 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -75,6 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories [TestCase(0, "Alpha", "Alpha (3)")] [TestCase(0, "Kilo (1)", "Kilo (1) (1)")] // though... we might consider "Kilo (2)" [TestCase(6, "Kilo (1)", "Kilo (1)")] // because of the id + [TestCase(0, "alpha", "alpha (3)")] [TestCase(0, "", " (1)")] [TestCase(0, null, " (1)")] public void Test(int nodeId, string nodeName, string expected) From 88d9e3321c4dce97dc6122c919ee3c6226631dd4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 00:39:12 +1000 Subject: [PATCH 034/218] Don't do a redirect on create for content, instead we do a 'soft redirect' which means the URL is updated but the route isn't changed which simplifies re-wiring everything up (will eventually do for all editors) --- .../components/content/edit.controller.js | 87 ++++++++----------- .../services/contenteditinghelper.service.js | 66 ++++++++------ .../common/services/editorstate.service.js | 26 +----- .../src/common/services/formhelper.service.js | 5 ++ .../src/common/services/navigation.service.js | 20 +++-- src/Umbraco.Web.UI.Client/src/init.js | 2 +- src/Umbraco.Web.UI.Client/src/routes.js | 1 + .../content.createblueprint.controller.js | 1 - .../views/content/content.edit.controller.js | 4 +- .../datatypes/datatype.edit.controller.js | 1 - .../dictionary/dictionary.edit.controller.js | 1 - .../views/documenttypes/edit.controller.js | 4 - .../views/macros/macros.edit.controller.js | 1 - .../src/views/media/media.edit.controller.js | 2 - .../src/views/mediatypes/edit.controller.js | 4 - .../views/member/member.edit.controller.js | 1 - .../src/views/membergroups/edit.controller.js | 1 - .../src/views/membertypes/edit.controller.js | 4 - .../partialviewmacros/edit.controller.js | 4 - .../src/views/partialviews/edit.controller.js | 4 - .../views/relationtypes/edit.controller.js | 1 - .../src/views/scripts/edit.controller.js | 4 - .../src/views/stylesheets/edit.controller.js | 4 - .../src/views/templates/edit.controller.js | 4 - .../src/views/users/group.controller.js | 4 - .../src/views/users/user.controller.js | 1 - .../services/content-editing-helper.spec.js | 3 - 27 files changed, 100 insertions(+), 160 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 38dce4985b..c5e2eaf755 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -8,6 +8,7 @@ var evts = []; var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode; + var watchingCulture = false; //setup scope vars $scope.defaultButton = null; @@ -26,40 +27,42 @@ $scope.allowOpen = true; $scope.app = null; - //called only one time when the editor first loads - function firstLoad(content) { - //Check if the editor state is holding a reference to a new item and merge it's notifications. - //We need to do this when creating new content because when it's created, we redirect to a new route and re-load the content from the server - //but we need to maintain it's 'notifications' data so we can re-bind it in the UI. - //The only time that editorState will contain something here is if it was preserved and that is only explicitly done when creating new content and redirecting. + //initializes any watches + function startWatches(content) { - var currState = editorState.getCurrent(); - if (currState && currState.udi === content.udi) { - content.notifications = currState.notifications; - for (var i = 0; i < content.variants.length; i++) { - content.variants[i].notifications = currState.variants[i].notifications; - } + //watch for changes to isNew & the content.id, set the page.isNew accordingly and load the breadcrumb if we can + $scope.$watchGroup(['isNew', 'content.id'], function (newVal, oldVal) { + + var contentId = newVal[1]; + $scope.page.isNew = Object.toBoolean(newVal[0]); - //Now check if the overlay service has an active overlay open that was owned by this editor prior to redirecting. - //This will occur if new variant content is created and there were notifications/errors for variants, the variant dialog will remain open - //but it will need to be refreshed with the new editor data since it will be holding a reference to stale scopes. - var currOverlay = overlayService.getCurrent(); - if (currOverlay && currOverlay.view.startsWith("views/content/overlays")) { - switch (currOverlay.view) { - case "views/content/overlays/publish.html": - overlayService.refresh(saveAndPublishDialog()); - break; + //We fetch all ancestors of the node to generate the footer breadcrumb navigation + if (!$scope.page.isNew && contentId && content.parentId && content.parentId !== -1) { + loadBreadcrumb(); + if (!watchingCulture) { + $scope.$watch('culture', + function (value, oldValue) { + if (value !== oldValue) { + loadBreadcrumb(); + } + }); } } + }); - } } //this initializes the editor with the data which will be called more than once if the data is re-loaded - function init(isFirstLoad) { + function init() { var content = $scope.content; + if (content.id && content.isChildOfListView && content.trashed === false) { + $scope.page.listViewPath = ($routeParams.page) ? + "/content/content/edit/" + content.parentId + "?page=" + $routeParams.page : + "/content/content/edit/" + content.parentId; + } + // we need to check wether an app is present in the current data, if not we will present the default app. var isAppPresent = false; @@ -92,25 +95,8 @@ $scope.appChanged(content.apps[0]); } - if (isFirstLoad) { - firstLoad(content); - } - editorState.set(content); - //We fetch all ancestors of the node to generate the footer breadcrumb navigation - if (!$scope.page.isNew) { - if (content.parentId && content.parentId !== -1) { - loadBreadcrumb(); - $scope.$watch('culture', - function (value, oldValue) { - if (value !== oldValue) { - loadBreadcrumb(); - } - }); - } - } - bindEvents(); resetVariantFlags(); @@ -178,7 +164,7 @@ /** * This does the content loading and initializes everything, called on first load */ - function loadContent(isFirstLoad) { + function loadContent() { //we are editing so get the content item from the server return $scope.getMethod()($scope.contentId) @@ -186,13 +172,7 @@ $scope.content = data; - if (data.isChildOfListView && data.trashed === false) { - $scope.page.listViewPath = ($routeParams.page) ? - "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page : - "/content/content/edit/" + data.parentId; - } - - init(isFirstLoad); + init(); syncTreeNode($scope.content, $scope.content.path, true); @@ -382,7 +362,8 @@ scope: $scope, content: $scope.content, action: args.action, - showNotifications: args.showNotifications + showNotifications: args.showNotifications, + softRedirect: true }).then(function (data) { //success init(); @@ -546,6 +527,7 @@ $scope.content = data; init(); + startWatches($scope.content); resetLastListPageNumber($scope.content); @@ -559,7 +541,8 @@ $scope.page.loading = true; - loadContent(true).then(function () { + loadContent().then(function () { + startWatches($scope.content); $scope.page.loading = false; }); } @@ -964,9 +947,7 @@ } //since we are not notifying and clearing server validation messages when they are received due to how the variant //switching works, we need to ensure they are cleared when this editor is destroyed - if (!$scope.page.isNew) { - serverValidationManager.clear(); - } + serverValidationManager.clear(); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index f54accbba0..732f682082 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -34,6 +34,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt return { + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + /** Used by the content editor and mini content editor to perform saving operations */ contentEditorPerformSave: function (args) { if (!angular.isObject(args)) { @@ -51,9 +54,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (args.showNotifications === undefined) { args.showNotifications = true; } - - var redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; - var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; + if (args.softRedirect === undefined) { + //when true, the url will change but it won't actually re-route + //this is merely here for compatibility, if only the content/media/members used this service we'd prob be ok but tons of editors + //use this service unfortunately and probably packages too. + args.softRedirect = false; + } var self = this; @@ -70,7 +76,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt self.handleSuccessfulSave({ scope: args.scope, savedContent: data, - redirectOnSuccess: redirectOnSuccess, + softRedirect: args.softRedirect, rebindCallback: function () { rebindCallback.apply(self, [args.content, data]); } @@ -84,7 +90,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt }, function (err) { self.handleSaveError({ showNotifications: args.showNotifications, - redirectOnFailure: redirectOnFailure, + softRedirect: args.softRedirect, err: err, rebindCallback: function () { rebindCallback.apply(self, [args.content, err.data]); @@ -574,15 +580,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * A function to handle what happens when we have validation issues from the server side * */ + + //TODO: Too many editors use this method for saving when this entire service should only be used for content/media/members, + // there is formHelper.handleError for other editors which should be used! + handleSaveError: function (args) { if (!args.err) { throw "args.err cannot be null"; } - if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { - throw "args.redirectOnFailure must be set to true or false"; - } - + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error @@ -600,14 +607,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt } } - // we need to detect what properties have changed and re-bind them with the server data. - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { - args.rebindCallback(); - } + if (!this.redirectToCreatedContent(args.err.data.id) || args.softRedirect) { + // If we are not redirecting it's because this is not newly created content, else in some cases we are + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). - if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { - // we are not redirecting because this is not new content, it is existing content. In this case - // notify all validators (don't clear the server validations though since we need to maintain their state because of + // In this case we need to detect what properties have changed and re-bind them with the server data. + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + + // In this case notify all validators (don't clear the server validations though since we need to maintain their state because of // how the variant switcher works in content). server validation state is always cleared when an editor first loads // and in theory when an editor is destroyed. serverValidationManager.notify(); @@ -631,6 +640,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect * when we're creating new content. */ + + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + handleSuccessfulSave: function (args) { if (!args) { @@ -640,14 +653,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt throw "args.savedContent cannot be null"; } - // the default behaviour is to redirect on success. This adds option to prevent when false - args.redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true; + if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id) || args.softRedirect) { - if (!args.redirectOnSuccess || !this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { + // If we are not redirecting it's because this is not newly created content, else in some cases we are + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. - //call the callback + // In this case we need to detect what properties have changed and re-bind them with the server data. if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { args.rebindCallback(); } @@ -665,7 +676,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * We need to decide if we need to redirect to edito mode or if we will remain in create mode. * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID */ - redirectToCreatedContent: function (id, modelState) { + redirectToCreatedContent: function (id) { //only continue if we are currently in create mode and not in infinite mode and if the resulting ID is valid if ($routeParams.create && (isValidIdentifier(id))) { @@ -677,10 +688,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //clear the query strings navigationService.clearSearch(["cculture"]); - - //preserve the editor state when we are redirecting to new content - editorState.preserve(); - + //change to new path $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this @@ -700,6 +708,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * For some editors like scripts or entites that have names as ids, these names can change and we need to redirect * to their new paths, this is helper method to do that. */ + + //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire + //service should only be used for content/media/members + redirectToRenamedContent: function (id) { //clear the query strings navigationService.clearSearch(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index acfef46e75..d00edae410 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -13,7 +13,6 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) { var current = null; - var preserveBetweenRoute = false; var state = { @@ -66,22 +65,6 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) return current; }, - /** - * @ngdoc function - * @name umbraco.services.angularHelper#preserve - * @methodOf umbraco.services.editorState - * @function - * - * @description - * When called it will flag the state to be preserved after the next route. - * Normally the editorState is cleared on each successful route but in some cases it should be preserved so calling this will preserve it. - * - * editorState.current can not be overwritten, you should only read values from it - * since modifying individual properties should be handled by the property editors - */ - preserve: function () { - preserveBetweenRoute = true; - } }; // TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. @@ -99,13 +82,8 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) //execute on each successful route (this is only bound once per application since a service is a singleton) $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { - if (!preserveBetweenRoute) { - //reset the editorState on each successful route chage - state.reset(); - } - - //always reset this - preserveBetweenRoute = false; + //reset the editorState on each successful route chage + state.reset(); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index f8d95ef1b3..263f09e4c1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -105,6 +105,11 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * @param {object} err The error object returned from the http promise */ handleError: function (err) { + + //TODO: Potentially add in the logic to showNotifications like the contentEditingHelper.handleSaveError does so that + // non content editors can just use this method instead of contentEditingHelper.handleSaveError which they should not use + // and they won't need to manually do it. + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index ba8334d307..7b2b07a9a2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -13,7 +13,7 @@ * Section navigation and search, and maintain their state for the entire application lifetime * */ -function navigationService($routeParams, $location, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) { +function navigationService($routeParams, $location, $route, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) { //the promise that will be resolved when the navigation is ready var navReadyPromise = $q.defer(); @@ -31,7 +31,8 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; var retainedQueryStrings = ["mculture"]; - + //A list of trees that don't cause a route when creating new items (TODO: eventually all trees should do this!) + var nonRoutingTreesOnCreate = ["content"]; function setMode(mode) { switch (mode) { @@ -115,16 +116,17 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve } var service = { - + /** * @ngdoc method * @name umbraco.services.navigationService#isRouteChangingNavigation * @methodOf umbraco.services.navigationService * * @description - * Detects if the route param differences will cause a navigation change or if the route param differences are + * Detects if the route param differences will cause a navigation/route change or if the route param differences are * only tracking state changes. - * This is used for routing operations where reloadOnSearch is false and when detecting form dirty changes when navigating to a different page. + * This is used for routing operations where "reloadOnSearch: false" or "reloadOnUrl: false", when detecting form dirty changes when navigating to a different page, + * and when we are creating new entities and moving from a route with the ?create=true parameter to an ID based parameter once it's created. * @param {object} currUrlParams Either a string path or a dictionary of route parameters * @param {object} nextUrlParams Either a string path or a dictionary of route parameters */ @@ -138,6 +140,14 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve nextUrlParams = pathToRouteParts(nextUrlParams); } + //first check if this is a ?create=true url being redirected to it's true url + if (currUrlParams.create === "true" && currUrlParams.id && currUrlParams.section && currUrlParams.tree && currUrlParams.method === "edit" && + !nextUrlParams.create && nextUrlParams.id && nextUrlParams.section === currUrlParams.section && nextUrlParams.tree === currUrlParams.tree && nextUrlParams.method === currUrlParams.method && + nonRoutingTreesOnCreate.indexOf(nextUrlParams.tree.toLowerCase()) >= 0) { + //this means we're coming from a path like /content/content/edit/1234?create=true to the created path like /content/content/edit/9999 + return false; + } + var allowRoute = true; //The only time that we want to not route is if only any of the nonRoutingQueryStrings have changed/added. diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 2e3aeb7e8d..e169d78b36 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -115,7 +115,7 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', }); //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. - //This is the case when a route uses reloadOnSearch: false which is the case for many or our routes so that we are able to maintain + //This is the case when a route uses "reloadOnSearch: false" or "reloadOnUrl: false" which is the case for many or our routes so that we are able to maintain //global state query strings without force re-loading views. //We can then detect if it's a location change that should force a route or not programatically. $rootScope.$on('$routeUpdate', function (event, next) { diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index e2a2cfe938..556a4d6aef 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -228,6 +228,7 @@ app.config(function ($routeProvider) { }, reloadOnSearch: false, + reloadOnUrl: false, resolve: canRoute(true) }) .otherwise({ redirectTo: '/login' }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js index c0002220e3..9a2c845d49 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.createblueprint.controller.js @@ -36,7 +36,6 @@ function(err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index cc55fbbf4d..5f8b4918cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -24,13 +24,15 @@ function ContentEditController($scope, $rootScope, $routeParams, contentResource $scope.page = $routeParams.page; $scope.isNew = infiniteMode ? $scope.model.create : $routeParams.create; //load the default culture selected in the main tree if any - $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + $scope.culture = $routeParams.cculture ? $routeParams.cculture : ($routeParams.mculture === "true"); //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change // and then we can pass in the updated culture to the editor $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; + $scope.isNew = next.params.create === "true"; + $scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index ead73beab8..6282ecb0b6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -174,7 +174,6 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic //NOTE: in the case of data type values we are setting the orig/new props // to be the same thing since that only really matters for content/media. contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js index 596f848abe..c05638f344 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/dictionary.edit.controller.js @@ -91,7 +91,6 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 62cf9d3e88..7c1f996931 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -317,10 +317,6 @@ saveMethod: contentTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js index e91d8ae366..79d837e516 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js @@ -35,7 +35,6 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta vm.page.saveButtonState = "success"; }, function (error) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: error }); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index d4d538b82c..5f8000569d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -184,7 +184,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, contentEditingHelper.handleSuccessfulSave({ scope: $scope, savedContent: data, - redirectOnSuccess: !infiniteMode, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); @@ -206,7 +205,6 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, contentEditingHelper.handleSaveError({ err: err, - redirectOnFailure: !infiniteMode, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js index 7ff3cee835..a0da3d0ea8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js @@ -269,10 +269,6 @@ saveMethod: mediaTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index a76862a1d9..5b9bc555ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -161,7 +161,6 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err, rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); diff --git a/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js index c963d5c0c4..9fb7321712 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membergroups/edit.controller.js @@ -86,7 +86,6 @@ function MemberGroupsEditController($scope, $routeParams, appState, navigationSe }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err }); diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js index f1d5db0ebe..dff5c65308 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js @@ -180,10 +180,6 @@ saveMethod: memberTypeResource.save, scope: $scope, content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, // we need to rebind... the IDs that have been created! rebindCallback: function (origContentType, savedContentType) { vm.contentType.id = savedContentType.id; diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js index 372cecb36c..a26681652e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js @@ -65,10 +65,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.partialViewMacro, - // We do not redirect on failure for partial view macros - this is because it is not possible to actually save the partial view - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { // create macro if needed diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js index 292898814d..51cca0b5af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js @@ -83,10 +83,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.partialView, - //We do not redirect on failure for partialviews - this is because it is not possible to actually save the partialviews - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js index 1667a89c35..7bcbe0716e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js @@ -119,7 +119,6 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, }, function (error) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: error }); diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js index 2ca93fba4c..34702230b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/edit.controller.js @@ -45,10 +45,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.script, - // We do not redirect on failure for scripts - this is because it is not possible to actually save the script - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js index 541d329e05..75e59605d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/edit.controller.js @@ -68,10 +68,6 @@ saveMethod: codefileResource.save, scope: $scope, content: vm.stylesheet, - // We do not redirect on failure for style sheets - this is because it is not possible to actually save the style sheet - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index d17f556b2f..69b60746c6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -83,10 +83,6 @@ saveMethod: templateResource.save, scope: $scope, content: vm.template, - // We do not redirect on failure for templates - this is because it is not possible to actually save the template - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) {} }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js index f37cc95174..d110e5d329 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.controller.js @@ -81,10 +81,6 @@ saveMethod: userGroupsResource.saveUserGroup, scope: $scope, content: vm.userGroup, - // We do not redirect on failure for users - this is because it is not possible to actually save a user - // when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, rebindCallback: function (orignal, saved) { } }).then(function (saved) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 2d9fccc399..a96ea664d7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -169,7 +169,6 @@ }, function (err) { contentEditingHelper.handleSaveError({ - redirectOnFailure: false, err: err, showNotifications: true }); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js index f3d2f8d759..2ccf9e886a 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js @@ -30,7 +30,6 @@ describe('contentEditingHelper tests', function () { //act var handled = contentEditingHelper.handleSaveError({ - redirectOnFailure: true, err: err, allNewProps: contentEditingHelper.getAllProps(content), allOrigProps: contentEditingHelper.getAllProps(content) @@ -49,7 +48,6 @@ describe('contentEditingHelper tests', function () { //act var handled = contentEditingHelper.handleSaveError({ - redirectOnFailure: true, err: err, allNewProps: [], allOrigProps: [] @@ -70,7 +68,6 @@ describe('contentEditingHelper tests', function () { //act var handled = contentEditingHelper.handleSaveError({ - redirectOnFailure: true, err: err, allNewProps: contentEditingHelper.getAllProps(content), allOrigProps: contentEditingHelper.getAllProps(content) From 0381c55ce78b91d046f48bd2e70576b6c3fe7832 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 01:01:09 +1000 Subject: [PATCH 035/218] reverts some parts of the content editor --- .../components/content/edit.controller.js | 149 ++++++++---------- 1 file changed, 70 insertions(+), 79 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index c5e2eaf755..d0fa5f039d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -439,83 +439,6 @@ }); } - /** returns the save and publish dialog model */ - function saveAndPublishDialog() { - var dialog = { - parentScope: $scope, - view: "views/content/overlays/publish.html", - variants: $scope.content.variants, //set a model property for the dialog - skipFormValidation: true, //when submitting the overlay form, skip any client side validation - submitButtonLabelKey: "buttons_saveAndPublish", - submit: function (model) { - model.submitButtonState = "busy"; - clearNotifications($scope.content); - //we need to return this promise so that the dialog can handle the result and wire up the validation response - return performSave({ - saveMethod: contentResource.publish, - action: "publish", - showNotifications: false - }).then(function (data) { - //show all notifications manually here since we disabled showing them automatically in the save method - formHelper.showNotifications(data); - clearNotifications($scope.content); - overlayService.close(); - return $q.when(data); - }, - function (err) { - clearDirtyState($scope.content.variants); - model.submitButtonState = "error"; - //re-map the dialog model since we've re-bound the properties - dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); - }); - }, - close: function () { - overlayService.close(); - } - }; - return dialog; - } - - /** Returns the unpublish dialog model */ - function unpublishDialog() { - var dialog = { - parentScope: $scope, - view: "views/content/overlays/unpublish.html", - variants: $scope.content.variants, //set a model property for the dialog - skipFormValidation: true, //when submitting the overlay form, skip any client side validation - submitButtonLabelKey: "content_unpublish", - submitButtonStyle: "warning", - submit: function (model) { - - model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, v => v.save && v.language); //ignore invariant - var culturesForUnpublishing = _.map(selectedVariants, v => v.language.culture); - - contentResource.unpublish($scope.content.id, culturesForUnpublishing) - .then(function (data) { - formHelper.resetForm({ scope: $scope }); - contentEditingHelper.reBindChangedProperties($scope.content, data); - init(); - syncTreeNode($scope.content, data.path); - $scope.page.buttonGroupState = "success"; - eventsService.emit("content.unpublished", { content: $scope.content }); - overlayService.close(); - }, function (err) { - $scope.page.buttonGroupState = 'error'; - }); - - - }, - close: function () { - overlayService.close(); - } - }; - return dialog; - } - if ($scope.page.isNew) { $scope.page.loading = true; @@ -550,7 +473,41 @@ $scope.unpublish = function () { clearNotifications($scope.content); if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { - overlayService.open(unpublishDialog()); + var dialog = { + parentScope: $scope, + view: "views/content/overlays/unpublish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "content_unpublish", + submitButtonStyle: "warning", + submit: function (model) { + + model.submitButtonState = "busy"; + + var selectedVariants = _.filter(model.variants, v => v.save && v.language); //ignore invariant + var culturesForUnpublishing = _.map(selectedVariants, v => v.language.culture); + + contentResource.unpublish($scope.content.id, culturesForUnpublishing) + .then(function (data) { + formHelper.resetForm({ scope: $scope }); + contentEditingHelper.reBindChangedProperties($scope.content, data); + init(); + syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = "success"; + eventsService.emit("content.unpublished", { content: $scope.content }); + overlayService.close(); + }, function (err) { + $scope.page.buttonGroupState = 'error'; + }); + + + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(dialog); } }; @@ -619,7 +576,41 @@ if (isContentCultureVariant()) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { - overlayService.open(saveAndPublishDialog()); + var dialog = { + parentScope: $scope, + view: "views/content/overlays/publish.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "buttons_saveAndPublish", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + //we need to return this promise so that the dialog can handle the result and wire up the validation response + return performSave({ + saveMethod: contentResource.publish, + action: "publish", + showNotifications: false + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, + function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); + }, + close: function () { + overlayService.close(); + } + }; + overlayService.open(dialog); } else { showValidationNotification(); From 7ba4c2f353a2baf7e8e266c6bb20b7006418a60f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 01:04:52 +1000 Subject: [PATCH 036/218] revert overlay.service --- .../src/common/services/overlay.service.js | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index e6564566a8..2165c1b7cb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -12,18 +12,6 @@ var currentOverlay = null; - function getCurrent() { - return currentOverlay; - } - - function refresh(overlay) { - //extend the passed overlay on top of the current overlay and then re-assign to currentOverlay - var merged = angular.extend({}, currentOverlay, overlay); - currentOverlay = merged; - - eventsService.emit("appState.overlay", currentOverlay); - } - function open(newOverlay) { // prevent two open overlays at the same time @@ -57,7 +45,7 @@ overlay.show = true; backdropService.open(backdropOptions); currentOverlay = overlay; - eventsService.emit("appState.overlay", currentOverlay); + eventsService.emit("appState.overlay", overlay); } function close() { @@ -80,9 +68,7 @@ var service = { open: open, close: close, - ysod: ysod, - refresh: refresh, - getCurrent: getCurrent + ysod: ysod }; return service; From fd5b5b67bfc8154315ab3a204184ac0af081213d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 10:11:27 +1000 Subject: [PATCH 037/218] Fixes notifications for invariant content publishing --- .../components/content/edit.controller.js | 2 +- .../components/events/events.directive.js | 302 +++++++++--------- .../src/common/services/formhelper.service.js | 32 +- src/Umbraco.Web/Editors/ContentController.cs | 31 +- 4 files changed, 181 insertions(+), 186 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index d0fa5f039d..6f331b58fb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -628,7 +628,7 @@ $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; - });; + }); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 77f2ffb54a..3541a1cf68 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -3,113 +3,113 @@ **/ angular.module('umbraco.directives') -.directive('onDragEnter', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnter); - }; - elm.on("dragenter", f); - scope.$on("$destroy", function(){ elm.off("dragenter", f);} ); - } - }; -}) - -.directive('onDragLeave', function () { - return function (scope, elm, attrs) { - var f = function (event) { - var rect = this.getBoundingClientRect(); - var getXY = function getCursorPosition(event) { - var x, y; - - if (typeof event.clientX === 'undefined') { - // try touch screen - x = event.pageX + document.documentElement.scrollLeft; - y = event.pageY + document.documentElement.scrollTop; - } else { - x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; - } - - return { x: x, y : y }; - }; - - var e = getXY(event.originalEvent); - - // Check the mouseEvent coordinates are outside of the rectangle - if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { - scope.$apply(attrs.onDragLeave); + .directive('onDragEnter', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnter); + }; + elm.on("dragenter", f); + scope.$on("$destroy", function () { elm.off("dragenter", f); }); } }; + }) - elm.on("dragleave", f); - scope.$on("$destroy", function(){ elm.off("dragleave", f);} ); - }; -}) + .directive('onDragLeave', function () { + return function (scope, elm, attrs) { + var f = function (event) { + var rect = this.getBoundingClientRect(); + var getXY = function getCursorPosition(event) { + var x, y; -.directive('onDragOver', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragOver); + if (typeof event.clientX === 'undefined') { + // try touch screen + x = event.pageX + document.documentElement.scrollLeft; + y = event.pageY + document.documentElement.scrollTop; + } else { + x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + return { x: x, y: y }; + }; + + var e = getXY(event.originalEvent); + + // Check the mouseEvent coordinates are outside of the rectangle + if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { + scope.$apply(attrs.onDragLeave); + } }; - elm.on("dragover", f); - scope.$on("$destroy", function(){ elm.off("dragover", f);} ); - } - }; -}) -.directive('onDragStart', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragStart); - }; - elm.on("dragstart", f); - scope.$on("$destroy", function(){ elm.off("dragstart", f);} ); - } - }; -}) + elm.on("dragleave", f); + scope.$on("$destroy", function () { elm.off("dragleave", f); }); + }; + }) -.directive('onDragEnd', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnd); - }; - elm.on("dragend", f); - scope.$on("$destroy", function(){ elm.off("dragend", f);} ); - } - }; -}) + .directive('onDragOver', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragOver); + }; + elm.on("dragover", f); + scope.$on("$destroy", function () { elm.off("dragover", f); }); + } + }; + }) -.directive('onDrop', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDrop); - }; - elm.on("drop", f); - scope.$on("$destroy", function(){ elm.off("drop", f);} ); - } - }; -}) + .directive('onDragStart', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragStart); + }; + elm.on("dragstart", f); + scope.$on("$destroy", function () { elm.off("dragstart", f); }); + } + }; + }) -.directive('onOutsideClick', function ($timeout, angularHelper) { - return function (scope, element, attrs) { + .directive('onDragEnd', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnd); + }; + elm.on("dragend", f); + scope.$on("$destroy", function () { elm.off("dragend", f); }); + } + }; + }) - var eventBindings = []; + .directive('onDrop', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDrop); + }; + elm.on("drop", f); + scope.$on("$destroy", function () { elm.off("drop", f); }); + } + }; + }) - function oneTimeClick(event) { + .directive('onOutsideClick', function ($timeout, angularHelper) { + return function (scope, element, attrs) { + + var eventBindings = []; + + function oneTimeClick(event) { var el = event.target.nodeName; //ignore link and button clicks - var els = ["INPUT","A","BUTTON"]; - if(els.indexOf(el) >= 0){return;} + var els = ["INPUT", "A", "BUTTON"]; + if (els.indexOf(el) >= 0) { return; } // ignore clicks on new overlay var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); - if(parents.length > 0){ + if (parents.length > 0) { return; } @@ -132,70 +132,70 @@ angular.module('umbraco.directives') } //ignore clicks inside this element - if( $(element).has( $(event.target) ).length > 0 ){ + if ($(element).has($(event.target)).length > 0) { return; } - scope.$apply(attrs.onOutsideClick); - } - - - $timeout(function(){ - - if ("bindClickOn" in attrs) { - - eventBindings.push(scope.$watch(function() { - return attrs.bindClickOn; - }, function(newValue) { - if (newValue === "true") { - $(document).on("click", oneTimeClick); - } else { - $(document).off("click", oneTimeClick); - } - })); - - } else { - $(document).on("click", oneTimeClick); + angularHelper.safeApply(scope, attrs.onOutsideClick); } - scope.$on("$destroy", function() { - $(document).off("click", oneTimeClick); - // unbind watchers - for (var e in eventBindings) { - eventBindings[e](); + $timeout(function () { + + if ("bindClickOn" in attrs) { + + eventBindings.push(scope.$watch(function () { + return attrs.bindClickOn; + }, function (newValue) { + if (newValue === "true") { + $(document).on("click", oneTimeClick); + } else { + $(document).off("click", oneTimeClick); + } + })); + + } else { + $(document).on("click", oneTimeClick); } + scope.$on("$destroy", function () { + $(document).off("click", oneTimeClick); + + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + + }); + }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + + }; + }) + + .directive('onRightClick', function ($parse) { + + document.oncontextmenu = function (e) { + if (e.target.hasAttribute('on-right-click')) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + return function (scope, el, attrs) { + el.on('contextmenu', function (e) { + e.preventDefault(); + e.stopPropagation(); + var fn = $parse(attrs.onRightClick); + scope.$apply(function () { + fn(scope, { $event: e }); + }); + return false; }); - }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + }; + }) - }; -}) - -.directive('onRightClick',function($parse){ - - document.oncontextmenu = function (e) { - if(e.target.hasAttribute('on-right-click')) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - return function(scope,el,attrs){ - el.on('contextmenu',function(e){ - e.preventDefault(); - e.stopPropagation(); - var fn = $parse(attrs.onRightClick); - scope.$apply(function () { - fn(scope, { $event: e }); - }); - return false; - }); - }; -}) - -.directive('onDelayedMouseleave', function ($timeout, $parse) { + .directive('onDelayedMouseleave', function ($timeout, $parse) { return { restrict: 'A', @@ -204,20 +204,20 @@ angular.module('umbraco.directives') var active = false; var fn = $parse(attrs.onDelayedMouseleave); - var leave_f = function(event) { - var callback = function() { - fn(scope, {$event:event}); + var leave_f = function (event) { + var callback = function () { + fn(scope, { $event: event }); }; active = false; - $timeout(function(){ - if(active === false){ + $timeout(function () { + if (active === false) { scope.$apply(callback); } }, 650); }; - var enter_f = function(event, args){ + var enter_f = function (event, args) { active = true; }; @@ -226,7 +226,7 @@ angular.module('umbraco.directives') element.on("mouseenter", enter_f); //unsub events - scope.$on("$destroy", function(){ + scope.$on("$destroy", function () { element.off("mouseleave", leave_f); element.off("mouseenter", enter_f); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 263f09e4c1..0555318bae 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -40,7 +40,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService else { currentForm = args.formCtrl; } - + //the first thing any form must do is broadcast the formSubmitting event args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); @@ -53,10 +53,10 @@ function formHelper(angularHelper, serverValidationManager, notificationsService //reset the server validations serverValidationManager.reset(); - + return true; }, - + /** * @ngdoc function * @name umbraco.services.formHelper#submitForm @@ -75,21 +75,21 @@ function formHelper(angularHelper, serverValidationManager, notificationsService if (!args.scope) { throw "args.scope cannot be null"; } - + args.scope.$broadcast("formSubmitted", { scope: args.scope }); }, showNotifications: function (args) { - if (!args || !args.notifications) { - return false; - } - if (angular.isArray(args.notifications)) { - for (var i = 0; i < args.notifications.length; i++) { - notificationsService.showNotification(args.notifications[i]); + if (!args || !args.notifications) { + return false; } - return true; - } - return false; + if (angular.isArray(args.notifications)) { + for (var i = 0; i < args.notifications.length; i++) { + notificationsService.showNotification(args.notifications[i]); + } + return true; + } + return false; }, /** @@ -104,7 +104,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * * @param {object} err The error object returned from the http promise */ - handleError: function (err) { + handleError: function (err) { //TODO: Potentially add in the logic to showNotifications like the contentEditingHelper.handleSaveError does so that // non content editors can just use this method instead of contentEditingHelper.handleSaveError which they should not use @@ -121,7 +121,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService this.handleServerValidation(err.data.ModelState); //execute all server validation events and subscribers - serverValidationManager.notifyAndClearAllSubscriptions(); + serverValidationManager.notifyAndClearAllSubscriptions(); } } else { @@ -129,7 +129,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService // TODO: All YSOD handling should be done with an interceptor overlayService.ysod(err); } - + }, /** diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index d48e586036..e85a449583 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -717,11 +717,7 @@ namespace Umbraco.Web.Editors case ContentSaveAction.PublishNew: { var publishStatus = PublishInternal(contentItem, defaultCulture, cultureForInvariantErrors, out wasCancelled, out var successfulCultures); - //global notifications - AddMessageForPublishStatus(new[] { publishStatus }, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures) - AddMessageForPublishStatus(new[] { publishStatus }, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(new[] { publishStatus }, globalNotifications, notifications, successfulCultures); } break; case ContentSaveAction.PublishWithDescendants: @@ -737,12 +733,7 @@ namespace Umbraco.Web.Editors } var publishStatus = PublishBranchInternal(contentItem, false, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList(); - - //global notifications - AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures ?? Array.Empty()) - AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures); } break; case ContentSaveAction.PublishWithDescendantsForce: @@ -758,12 +749,7 @@ namespace Umbraco.Web.Editors } var publishStatus = PublishBranchInternal(contentItem, true, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList(); - - //global notifications - AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); - //variant specific notifications - foreach (var c in successfulCultures ?? Array.Empty()) - AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures); + AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures); } break; default: @@ -801,6 +787,15 @@ namespace Umbraco.Web.Editors return display; } + private void AddPublishStatusNotifications(IReadOnlyCollection publishStatus, SimpleNotificationModel globalNotifications, Dictionary variantNotifications, string[] successfulCultures) + { + //global notifications + AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); + //variant specific notifications + foreach (var c in successfulCultures ?? Array.Empty()) + AddMessageForPublishStatus(publishStatus, variantNotifications.GetOrCreate(c), successfulCultures); + } + /// /// Validates critical data for persistence and updates the ModelState and result accordingly /// @@ -1215,7 +1210,7 @@ namespace Umbraco.Web.Editors //its invariant, proceed normally var publishStatus = Services.ContentService.SaveAndPublish(contentItem.PersistedContent, userId: Security.CurrentUser.Id); wasCancelled = publishStatus.Result == PublishResultType.FailedPublishCancelledByEvent; - successfulCultures = Array.Empty(); + successfulCultures = null; //must be null! this implies invariant return publishStatus; } From e0059f1fadfa2bd138f67285b19994d0c1565936 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 10:35:21 +1000 Subject: [PATCH 038/218] adds notes --- .../src/views/content/content.edit.controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 5f8b4918cf..f631e92954 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -28,7 +28,9 @@ function ContentEditController($scope, $rootScope, $routeParams, contentResource //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change - // and then we can pass in the updated culture to the editor + //and then we can pass in the updated culture to the editor. + //This will also execute when we are redirecting from creating an item to a newly created item since that + //will not cause a route change and so we can update the isNew and contentId flags accordingly. $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; $scope.isNew = next.params.create === "true"; From 1307ee7ced4b90189c9824658a9df4b092d2dd77 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 10:36:52 +1000 Subject: [PATCH 039/218] some cleanup --- .../src/common/services/navigation.service.js | 2 +- .../src/views/content/content.edit.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 7b2b07a9a2..9bd34af43f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -13,7 +13,7 @@ * Section navigation and search, and maintain their state for the entire application lifetime * */ -function navigationService($routeParams, $location, $route, $q, $timeout, $injector, eventsService, umbModelMapper, treeService, appState) { +function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState) { //the promise that will be resolved when the navigation is ready var navReadyPromise = $q.defer(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index f631e92954..5cbf25ba7d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the content editor */ -function ContentEditController($scope, $rootScope, $routeParams, contentResource) { +function ContentEditController($scope, $routeParams, contentResource) { var infiniteMode = $scope.model && $scope.model.infiniteMode; From 2797011fb0dee777cbe8a6946bda3a495e2d0a43 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 11:34:36 +1000 Subject: [PATCH 040/218] Ensures there's no duplicate notifications and ensures that soft redirects occur for content blueprints too --- .../src/common/services/navigation.service.js | 2 +- .../Models/ContentEditing/MessagesExtensions.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 9bd34af43f..e51b7b818e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -32,7 +32,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService var nonRoutingQueryStrings = ["mculture", "cculture", "lq"]; var retainedQueryStrings = ["mculture"]; //A list of trees that don't cause a route when creating new items (TODO: eventually all trees should do this!) - var nonRoutingTreesOnCreate = ["content"]; + var nonRoutingTreesOnCreate = ["content", "contentblueprints"]; function setMode(mode) { switch (mode) { diff --git a/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs b/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs index 3a8496ac3f..1f526a50f3 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MessagesExtensions.cs @@ -1,10 +1,15 @@  +using System.Linq; +using Umbraco.Core; + namespace Umbraco.Web.Models.ContentEditing { public static class MessagesExtensions { public static void AddNotification(this INotificationModel model, string header, string msg, NotificationStyle type) { + if (model.Exists(header, msg, type)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -15,6 +20,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddSuccessNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Success)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -25,6 +32,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddErrorNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Error)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -35,6 +44,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddWarningNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Warning)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -45,6 +56,8 @@ namespace Umbraco.Web.Models.ContentEditing public static void AddInfoNotification(this INotificationModel model, string header, string msg) { + if (model.Exists(header, msg, NotificationStyle.Info)) return; + model.Notifications.Add(new Notification() { Header = header, @@ -52,5 +65,7 @@ namespace Umbraco.Web.Models.ContentEditing NotificationType = NotificationStyle.Info }); } + + private static bool Exists(this INotificationModel model, string header, string message, NotificationStyle notificationType) => model.Notifications.Any(x => x.Header.InvariantEquals(header) && x.Message.InvariantEquals(message) && x.NotificationType == notificationType); } } From 8a19cd4bbc1422bb99a3c0f0c17b76ae98eb99e7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 12:22:14 +1000 Subject: [PATCH 041/218] Fixes content blueprint creation --- .../components/content/edit.controller.js | 10 +++++++--- .../contentblueprints/edit.controller.js | 6 +++++- src/Umbraco.Web/Editors/ContentController.cs | 19 ++++++++++++++----- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 6f331b58fb..a548820138 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -242,9 +242,13 @@ } if (!$scope.content.isChildOfListView) { - navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); + navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }) + .then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }, function () { + //handle the rejection + console.log("A problem occurred syncing the tree! A path is probably incorrect.") + }); } else if (initialLoad === true) { diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js index 59351ffd38..982af76d69 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js @@ -47,9 +47,13 @@ function ContentBlueprintEditController($scope, $routeParams, contentResource) { //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change - // and then we can pass in the updated culture to the editor + //and then we can pass in the updated culture to the editor. + //This will also execute when we are redirecting from creating an item to a newly created item since that + //will not cause a route change and so we can update the isNew and contentId flags accordingly. $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; + $scope.isNew = $routeParams.id === "-1"; + $scope.contentId = $routeParams.id; }); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index e85a449583..754d5aca1e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -576,8 +576,13 @@ namespace Umbraco.Web.Editors Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); //we need to reuse the underlying logic so return the result that it wants return OperationResult.Succeed(new EventMessages()); + }, + content => + { + var display = MapToDisplay(content); + SetupBlueprint(display, content); + return display; }); - SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); return contentItemDisplay; } @@ -591,11 +596,15 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public ContentItemDisplay PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { - var contentItemDisplay = PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + var contentItemDisplay = PostSaveInternal( + contentItem, + content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id), + MapToDisplay); + return contentItemDisplay; } - private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod) + private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func mapToDisplay) { //Recent versions of IE/Edge may send in the full client side file path instead of just the file name. //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all @@ -626,7 +635,7 @@ namespace Umbraco.Web.Editors { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the model state to the outgoing object and throw a validation message - var forDisplay = MapToDisplay(contentItem.PersistedContent); + var forDisplay = mapToDisplay(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); } @@ -757,7 +766,7 @@ namespace Umbraco.Web.Editors } //get the updated model - var display = MapToDisplay(contentItem.PersistedContent); + var display = mapToDisplay(contentItem.PersistedContent); //merge the tracked success messages with the outgoing model display.Notifications.AddRange(globalNotifications.Notifications); From 8eafadc29b2fe50ff81b3f9ffe7b81d2df69dfa3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 14:00:28 +1000 Subject: [PATCH 042/218] adds some more info to panic exceptions --- .../PublishedCache/NuCache/ContentStore.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 298b98ca05..f3c3c2bf7a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -643,7 +643,7 @@ namespace Umbraco.Web.PublishedCache.NuCache while (id > 0) { if (!_contentNodes.TryGetValue(id, out var link) || link.Value == null) - throw new Exception("panic: failed to get child"); + throw new Exception("panic: failed to get child " + id); ClearBranchLocked(link.Value); id = link.Value.NextSiblingContentId; } @@ -667,7 +667,7 @@ namespace Umbraco.Web.PublishedCache.NuCache else { if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null) - throw new Exception("panic: failed to get parent"); + throw new Exception("panic: failed to get parent " + content.ParentContentId); } var parent = parentLink.Value; @@ -680,11 +680,11 @@ namespace Umbraco.Web.PublishedCache.NuCache else { if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var link) || link.Value == null) - throw new Exception("panic: failed to get first child"); + throw new Exception("panic: failed to get first child " + parent.FirstChildContentId); while (link.Value.NextSiblingContentId != content.Id) if (!_contentNodes.TryGetValue(parent.NextSiblingContentId, out link) || link.Value == null) - throw new Exception("panic: failed to get next sibling"); + throw new Exception("panic: failed to get next sibling " + parent.NextSiblingContentId); var prevChild = GenCloneLocked(link); prevChild.NextSiblingContentId = content.NextSiblingContentId; @@ -735,7 +735,7 @@ namespace Umbraco.Web.PublishedCache.NuCache else { if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null) - throw new Exception("panic: failed to get parent"); + throw new Exception("panic: failed to get parent " + content.ParentContentId); } var parent = parentLink.Value; @@ -748,7 +748,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var prevChildLink) || prevChildLink.Value == null) - throw new Exception("panic: failed to get first child"); + throw new Exception("panic: failed to get first child " + parent.FirstChildContentId); var prevChild = prevChildLink.Value; @@ -764,7 +764,7 @@ namespace Umbraco.Web.PublishedCache.NuCache while (prevChild.NextSiblingContentId > 0) { if (!_contentNodes.TryGetValue(prevChild.NextSiblingContentId, out var link) || link.Value == null) - throw new Exception("panic: failed to get next child"); + throw new Exception("panic: failed to get next child " + prevChild.NextSiblingContentId); var nextChild = link.Value; if (nextChild.SortOrder > content.SortOrder) @@ -873,7 +873,7 @@ namespace Umbraco.Web.PublishedCache.NuCache while (id > 0) { if (!_contentNodes.TryGetValue(id, out var link) || link == null) - throw new Exception("panic: failed to get sibling"); + throw new Exception("panic: failed to get sibling " + id); yield return link.Value; id = link.Value.NextSiblingContentId; } From dec042fce4dbd1cbeeb33d8dc4794327db2e2098 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 12 Jun 2019 16:28:33 +1000 Subject: [PATCH 043/218] adds Id to panic exception --- src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 1995e8b424..de0c46f6ea 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -278,7 +278,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var content = getById(publishedSnapshot, IsPreviewing, id); if (content == null) - throw new Exception("panic: failed to get content"); + throw new Exception("panic: failed to get content " + id); yield return content; From 9c252131f7ca0a64ec066e39fa6afdbfb99651d7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 12 Jun 2019 08:55:03 +0200 Subject: [PATCH 044/218] Better exception message in NuCache --- .../PublishedCache/NuCache/ContentStore.cs | 56 ++++++++----------- .../NuCache/PublishedContent.cs | 2 +- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index f3c3c2bf7a..f1f8f137fb 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -642,13 +642,22 @@ namespace Umbraco.Web.PublishedCache.NuCache var id = content.FirstChildContentId; while (id > 0) { - if (!_contentNodes.TryGetValue(id, out var link) || link.Value == null) - throw new Exception("panic: failed to get child " + id); + var link = GetLinkedNode(id, "child"); ClearBranchLocked(link.Value); id = link.Value.NextSiblingContentId; } } + // gets the link node + // throws (panic) if not found, or no value + private LinkedNode GetLinkedNode(int id, string description) + { + if (_contentNodes.TryGetValue(id, out var link) && link.Value != null) + return link; + + throw new Exception($"panic: failed to get {description} with id={id}"); + } + private LinkedNode GetParentLink(ContentNode content) { _contentNodes.TryGetValue(content.ParentContentId, out var link); // else null @@ -659,16 +668,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private void RemoveNodeLocked(ContentNode content) { - LinkedNode parentLink; - if (content.ParentContentId < 0) - { - parentLink = _root; - } - else - { - if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null) - throw new Exception("panic: failed to get parent " + content.ParentContentId); - } + var parentLink = content.ParentContentId < 0 + ? _root + : GetLinkedNode(content.ParentContentId, "parent"); var parent = parentLink.Value; @@ -679,12 +681,10 @@ namespace Umbraco.Web.PublishedCache.NuCache } else { - if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var link) || link.Value == null) - throw new Exception("panic: failed to get first child " + parent.FirstChildContentId); + var link = GetLinkedNode(parent.FirstChildContentId, "first child"); while (link.Value.NextSiblingContentId != content.Id) - if (!_contentNodes.TryGetValue(parent.NextSiblingContentId, out link) || link.Value == null) - throw new Exception("panic: failed to get next sibling " + parent.NextSiblingContentId); + link = GetLinkedNode(parent.NextSiblingContentId, "next child"); var prevChild = GenCloneLocked(link); prevChild.NextSiblingContentId = content.NextSiblingContentId; @@ -726,17 +726,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private void AddNodeLocked(ContentNode content) { - LinkedNode parentLink; - - if (content.ParentContentId < 0) - { - parentLink = _root; - } - else - { - if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null) - throw new Exception("panic: failed to get parent " + content.ParentContentId); - } + var parentLink = content.ParentContentId < 0 + ? _root + : GetLinkedNode(content.ParentContentId, "parent"); var parent = parentLink.Value; @@ -747,9 +739,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return; } - if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var prevChildLink) || prevChildLink.Value == null) - throw new Exception("panic: failed to get first child " + parent.FirstChildContentId); - + var prevChildLink = GetLinkedNode(parent.FirstChildContentId, "first child"); var prevChild = prevChildLink.Value; if (prevChild.SortOrder > content.SortOrder) @@ -763,8 +753,7 @@ namespace Umbraco.Web.PublishedCache.NuCache while (prevChild.NextSiblingContentId > 0) { - if (!_contentNodes.TryGetValue(prevChild.NextSiblingContentId, out var link) || link.Value == null) - throw new Exception("panic: failed to get next child " + prevChild.NextSiblingContentId); + var link = GetLinkedNode(prevChild.NextSiblingContentId, "next child"); var nextChild = link.Value; if (nextChild.SortOrder > content.SortOrder) @@ -872,8 +861,7 @@ namespace Umbraco.Web.PublishedCache.NuCache while (id > 0) { - if (!_contentNodes.TryGetValue(id, out var link) || link == null) - throw new Exception("panic: failed to get sibling " + id); + var link = GetLinkedNode(id, "sibling"); yield return link.Value; id = link.Value.NextSiblingContentId; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 1995e8b424..20372c074d 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -278,7 +278,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var content = getById(publishedSnapshot, IsPreviewing, id); if (content == null) - throw new Exception("panic: failed to get content"); + throw new Exception($"panic: failed to get content with id={id}"); yield return content; From 5b546cc425a930888388edb03a04e4449b818996 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 12 Jun 2019 10:39:34 +0200 Subject: [PATCH 045/218] Bugfix: Don't increase the page number in the loop, because the items are moved to another subtree, and therefore is not any longer part of the descendants call. Now we avoid the leak of items when moving to recycle bin. --- src/Umbraco.Core/Services/Implement/ContentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 5cc1a584b1..79f593d8d2 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1930,7 +1930,7 @@ namespace Umbraco.Core.Services.Implement var total = long.MaxValue; while (page * pageSize < total) { - var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + var descendants = GetPagedDescendantsLocked(originalPath, page, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path From cc4a7653e50bdaefd1cf0d134c9e9299b259fae5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 12 Jun 2019 14:31:01 +0200 Subject: [PATCH 046/218] Bugfix: Still load trees even when a tree is about to be loaded. But only use the result, if we are still on the correct section. This eliminates the issue where a wrong tree is shown in a section. --- .../components/tree/umbtree.directive.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 7055d1a746..6a94d3e43b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -229,11 +229,9 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } /** Method to load in the tree data */ - function loadTree() { - if (!$scope.loading && $scope.section) { - $scope.loading = true; - + if ($scope.section) { + //default args var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false }; @@ -244,20 +242,22 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use return treeService.getTree(args) .then(function (data) { - + //Only use the tree data, if we are still on the correct section + if(data.alias !== $scope.section){ + return $q.reject(); + } + //set the data once we have it $scope.tree = data; - $scope.loading = false; - //set the root as the current active tree $scope.activeTree = $scope.tree.root; emitEvent("treeLoaded", { tree: $scope.tree }); emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children }); + return $q.when(data); }, function (reason) { - $scope.loading = false; notificationsService.error("Tree Error", reason); return $q.reject(reason); }); From f4593778cb01dcff394dd674a7e83eef806650de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 13 Jun 2019 12:31:29 +0200 Subject: [PATCH 047/218] updated package lock --- src/Umbraco.Web.UI.Client/package-lock.json | 143 +++++++++----------- 1 file changed, 62 insertions(+), 81 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index c1c85a9688..ca92a8da49 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -937,7 +937,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -955,7 +955,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, @@ -1176,7 +1176,7 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true }, "array-sort": { @@ -1535,7 +1535,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "requires": { @@ -1551,7 +1551,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -1566,7 +1566,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -1740,7 +1740,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -1750,7 +1750,7 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { @@ -2436,7 +2436,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -2559,7 +2559,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", "dev": true }, "core-util-is": { @@ -3493,7 +3493,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -4061,7 +4061,7 @@ }, "engine.io-client": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", "dev": true, "requires": { @@ -4263,7 +4263,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true } @@ -4358,7 +4358,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4368,13 +4368,13 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, "espree": { @@ -4397,7 +4397,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4406,7 +4406,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4482,7 +4482,7 @@ "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4691,7 +4691,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", "dev": true, "requires": { "is-number": "^2.1.0", @@ -5244,7 +5244,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true }, "fs-extra": { @@ -5309,8 +5309,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5331,14 +5330,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5353,20 +5350,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5483,8 +5477,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5496,7 +5489,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5511,7 +5503,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5519,14 +5510,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5545,7 +5534,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5626,8 +5614,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5639,7 +5626,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5725,8 +5711,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5762,7 +5747,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5782,7 +5766,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5826,14 +5809,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -6030,7 +6011,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6621,7 +6602,7 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", + "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", "dev": true, "requires": { "eslint": "^5.0.1", @@ -8072,7 +8053,7 @@ "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -8367,7 +8348,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -8376,7 +8357,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { @@ -8424,7 +8405,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -8581,7 +8562,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8708,7 +8689,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } @@ -9335,7 +9316,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true }, "lpad-align": { @@ -9369,7 +9350,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "requires": { "pify": "^3.0.0" @@ -9386,7 +9367,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -9583,7 +9564,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimatch": { @@ -12735,7 +12716,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, @@ -13126,7 +13107,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, "posix-character-classes": { @@ -13812,7 +13793,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -14310,7 +14291,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "sax": { @@ -14497,7 +14478,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, "shebang-command": { @@ -14775,7 +14756,7 @@ }, "socket.io-parser": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", "dev": true, "requires": { @@ -14920,7 +14901,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "optional": true, @@ -15067,7 +15048,7 @@ "stream-consume": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha1-0721mMK9CugrjKx6xQsRB6eZbEg=", + "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", "dev": true }, "stream-shift": { @@ -15079,7 +15060,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { "date-format": "^1.2.0", @@ -15106,7 +15087,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -15121,7 +15102,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15138,7 +15119,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -15328,7 +15309,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "requires": { "escape-string-regexp": "^1.0.2" @@ -15488,7 +15469,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15696,7 +15677,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15731,7 +15712,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true }, "to-fast-properties": { @@ -16064,13 +16045,13 @@ "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { "punycode": "^2.1.0" From e39e132febdd41469af3e0d4810ef5c8bf06a321 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 17 Jun 2019 07:16:37 +0200 Subject: [PATCH 048/218] Cleaned up, such that we are not in doubt about whether the page variable should have been increased. --- src/Umbraco.Core/Services/Implement/ContentService.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 79f593d8d2..c79e7aa869 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1695,12 +1695,11 @@ namespace Umbraco.Core.Services.Implement } const int pageSize = 500; - var page = 0; var total = long.MaxValue; - while (page * pageSize < total) + while (total > 0) { //get descendants - ordered from deepest to shallowest - var descendants = GetPagedDescendants(content.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); + var descendants = GetPagedDescendants(content.Id, 0, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); foreach (var c in descendants) DoDelete(c); } @@ -1926,11 +1925,10 @@ namespace Umbraco.Core.Services.Implement paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString) : parent.Path) + "," + content.Id; const int pageSize = 500; - var page = 0; var total = long.MaxValue; - while (page * pageSize < total) + while (total > 0) { - var descendants = GetPagedDescendantsLocked(originalPath, page, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); + var descendants = GetPagedDescendantsLocked(originalPath, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending)); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path From 46f4984add8c199f189e933b71f1ca7fa22619eb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 17 Jun 2019 07:18:23 +0200 Subject: [PATCH 049/218] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1357: Fixed issue where too fast change between sections, leads to a wrong tree (The first requested) shown in the menu. --- .../src/common/directives/components/tree/umbtree.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 6a94d3e43b..fbc0ea1eb0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -243,7 +243,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use return treeService.getTree(args) .then(function (data) { //Only use the tree data, if we are still on the correct section - if(data.alias !== $scope.section){ + if(data.alias !== $scope.section){ return $q.reject(); } From 977c1d9585fedad69f67213e2a3cea7aec4b499e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Jun 2019 16:23:38 +1000 Subject: [PATCH 050/218] fixes JS error when navigating to the content info tab --- src/Umbraco.Web.UI.Client/package-lock.json | 381 ++++++++++++++---- .../src/views/components/umb-pagination.html | 2 +- 2 files changed, 297 insertions(+), 86 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index ca92a8da49..90fb2437de 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -937,7 +937,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -1104,6 +1104,7 @@ "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-3.2.0.tgz", "integrity": "sha1-nNnABpV+vpX62tW9YJiUKoE3N/Y=", "dev": true, + "optional": true, "requires": { "file-type": "^3.1.0" }, @@ -1112,7 +1113,8 @@ "version": "3.9.0", "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true } } }, @@ -1535,9 +1537,10 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -1547,13 +1550,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1569,6 +1574,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1757,7 +1763,8 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "dev": true, + "optional": true }, "buffer-fill": { "version": "1.0.0", @@ -1776,6 +1783,7 @@ "resolved": "https://registry.npmjs.org/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz", "integrity": "sha1-APFfruOreh3aLN5tkSG//dB7ImI=", "dev": true, + "optional": true, "requires": { "file-type": "^3.1.0", "readable-stream": "^2.0.2", @@ -1787,19 +1795,22 @@ "version": "3.9.0", "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1815,6 +1826,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1823,13 +1835,15 @@ "version": "2.0.3", "resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", - "dev": true + "dev": true, + "optional": true }, "vinyl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -1950,7 +1964,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "dev": true + "dev": true, + "optional": true }, "caseless": { "version": "0.12.0", @@ -1963,6 +1978,7 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-1.2.0.tgz", "integrity": "sha1-/7Im/n78VHKI3GLuPpcHPCEtEDQ=", "dev": true, + "optional": true, "requires": { "get-proxy": "^1.0.1", "is-obj": "^1.0.0", @@ -1974,7 +1990,8 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true + "dev": true, + "optional": true } } }, @@ -2249,7 +2266,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=", - "dev": true + "dev": true, + "optional": true }, "coa": { "version": "2.0.1", @@ -2373,6 +2391,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, + "optional": true, "requires": { "graceful-readlink": ">= 1.0.0" } @@ -2585,6 +2604,7 @@ "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, + "optional": true, "requires": { "capture-stack-trace": "^1.0.0" } @@ -2839,6 +2859,7 @@ "resolved": "https://registry.npmjs.org/decompress/-/decompress-3.0.0.tgz", "integrity": "sha1-rx3VDQbjv8QyRh033hGzjA2ZG+0=", "dev": true, + "optional": true, "requires": { "buffer-to-vinyl": "^1.0.0", "concat-stream": "^1.4.6", @@ -2856,6 +2877,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -2864,13 +2886,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, + "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -2882,6 +2906,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, + "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -2891,6 +2916,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -2900,6 +2926,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, + "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -2913,6 +2940,7 @@ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, + "optional": true, "requires": { "extend": "^3.0.0", "glob": "^5.0.3", @@ -2928,13 +2956,15 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -2946,13 +2976,15 @@ "version": "0.10.31", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "dev": true, + "optional": true }, "through2": { "version": "0.6.5", "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -2964,19 +2996,22 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -2985,13 +3020,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3001,6 +3038,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, + "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -3021,13 +3059,15 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, + "optional": true, "requires": { "is-stream": "^1.0.1", "readable-stream": "^2.0.1" @@ -3038,6 +3078,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3053,6 +3094,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3062,6 +3104,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -3071,6 +3114,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, + "optional": true, "requires": { "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" @@ -3081,6 +3125,7 @@ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, + "optional": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -3091,6 +3136,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -3103,6 +3149,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3114,6 +3161,7 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, + "optional": true, "requires": { "duplexify": "^3.2.0", "glob-stream": "^5.3.2", @@ -3141,6 +3189,7 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-3.1.0.tgz", "integrity": "sha1-IXx4n5uURQ76rcXF5TeXj8MzxGY=", "dev": true, + "optional": true, "requires": { "is-tar": "^1.0.0", "object-assign": "^2.0.0", @@ -3154,19 +3203,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3179,6 +3231,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3189,6 +3242,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3201,6 +3255,7 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz", "integrity": "sha1-iyOTVoE1X58YnYclag+L3ZbZZm0=", "dev": true, + "optional": true, "requires": { "is-bzip2": "^1.0.0", "object-assign": "^2.0.0", @@ -3215,19 +3270,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3240,6 +3298,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3250,6 +3309,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3262,6 +3322,7 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-3.1.0.tgz", "integrity": "sha1-ssE9+YFmJomRtxXWRH9kLpaW9aA=", "dev": true, + "optional": true, "requires": { "is-gzip": "^1.0.0", "object-assign": "^2.0.0", @@ -3275,19 +3336,22 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3300,6 +3364,7 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3310,6 +3375,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, + "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3322,6 +3388,7 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-3.4.0.tgz", "integrity": "sha1-YUdbQVIGa74/7hL51inRX+ZHjus=", "dev": true, + "optional": true, "requires": { "is-zip": "^1.0.0", "read-all-stream": "^3.0.0", @@ -3337,6 +3404,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3349,7 +3417,8 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "dev": true, + "optional": true }, "deep-is": { "version": "0.1.3", @@ -3568,6 +3637,7 @@ "resolved": "https://registry.npmjs.org/download/-/download-4.4.3.tgz", "integrity": "sha1-qlX9rTktldS2jowr4D4MKqIbqaw=", "dev": true, + "optional": true, "requires": { "caw": "^1.0.1", "concat-stream": "^1.4.7", @@ -3591,6 +3661,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -3599,13 +3670,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, + "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -3617,6 +3690,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, + "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -3626,6 +3700,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3635,6 +3710,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, + "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -3648,6 +3724,7 @@ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, + "optional": true, "requires": { "extend": "^3.0.0", "glob": "^5.0.3", @@ -3663,13 +3740,15 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3681,13 +3760,15 @@ "version": "0.10.31", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "dev": true, + "optional": true }, "through2": { "version": "0.6.5", "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, + "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3699,19 +3780,22 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3720,13 +3804,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3736,6 +3822,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, + "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -3756,13 +3843,15 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, + "optional": true, "requires": { "is-stream": "^1.0.1", "readable-stream": "^2.0.1" @@ -3773,6 +3862,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3788,6 +3878,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3797,6 +3888,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -3806,6 +3898,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, + "optional": true, "requires": { "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" @@ -3816,6 +3909,7 @@ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, + "optional": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -3826,6 +3920,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -3838,6 +3933,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3849,6 +3945,7 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, + "optional": true, "requires": { "duplexify": "^3.2.0", "glob-stream": "^5.3.2", @@ -3891,6 +3988,7 @@ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", "dev": true, + "optional": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -3903,6 +4001,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, + "optional": true, "requires": { "once": "^1.4.0" } @@ -3911,13 +4010,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3927,6 +4028,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3942,6 +4044,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3953,6 +4056,7 @@ "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz", "integrity": "sha1-3uUim98KtrogEqOV4bhpq/iBNHM=", "dev": true, + "optional": true, "requires": { "onetime": "^1.0.0", "set-immediate-shim": "^1.0.0" @@ -3962,7 +4066,8 @@ "version": "1.1.0", "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4945,6 +5050,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, + "optional": true, "requires": { "pend": "~1.2.0" } @@ -4992,13 +5098,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=", - "dev": true + "dev": true, + "optional": true }, "filenamify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", "dev": true, + "optional": true, "requires": { "filename-reserved-regex": "^1.0.0", "strip-outer": "^1.0.0", @@ -5245,7 +5353,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", - "dev": true + "dev": true, + "optional": true }, "fs-extra": { "version": "1.0.0", @@ -5309,7 +5418,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5330,12 +5440,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5350,17 +5462,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5477,7 +5592,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5489,6 +5605,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5503,6 +5620,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5510,12 +5628,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5534,6 +5654,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5614,7 +5735,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5626,6 +5748,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5711,7 +5834,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5747,6 +5871,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5766,6 +5891,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5809,12 +5935,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -5850,6 +5978,7 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-1.1.0.tgz", "integrity": "sha1-iUhUSRvFkbDxR9euVw9cZ4tyVus=", "dev": true, + "optional": true, "requires": { "rc": "^1.1.2" } @@ -6156,6 +6285,7 @@ "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "dev": true, + "optional": true, "requires": { "create-error-class": "^3.0.1", "duplexer2": "^0.1.4", @@ -6179,6 +6309,7 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.2" } @@ -6187,19 +6318,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, + "optional": true, "requires": { "error-ex": "^1.2.0" } @@ -6209,6 +6343,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6224,6 +6359,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -6243,7 +6379,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true + "dev": true, + "optional": true }, "growly": { "version": "1.3.0", @@ -6560,6 +6697,7 @@ "resolved": "https://registry.npmjs.org/gulp-decompress/-/gulp-decompress-1.2.0.tgz", "integrity": "sha1-jutlpeAV+O2FMsr+KEVJYGJvDcc=", "dev": true, + "optional": true, "requires": { "archive-type": "^3.0.0", "decompress": "^3.0.0", @@ -6571,13 +6709,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6593,6 +6733,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -7210,6 +7351,7 @@ "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", "dev": true, + "optional": true, "requires": { "convert-source-map": "^1.1.1", "graceful-fs": "^4.1.2", @@ -7222,13 +7364,15 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "dev": true, + "optional": true }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, + "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -7238,6 +7382,7 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, + "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -8120,7 +8265,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz", "integrity": "sha1-XuWOqlounIDiFAe+3yOuWsCRs/w=", - "dev": true + "dev": true, + "optional": true }, "is-callable": { "version": "1.1.4", @@ -8255,7 +8401,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=", - "dev": true + "dev": true, + "optional": true }, "is-jpg": { "version": "1.0.1", @@ -8268,7 +8415,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.1.1.tgz", "integrity": "sha1-fUxXKDd+84bD4ZSpkRv1fG3DNec=", - "dev": true + "dev": true, + "optional": true }, "is-number": { "version": "3.0.0", @@ -8334,7 +8482,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true + "dev": true, + "optional": true }, "is-regex": { "version": "1.0.4", @@ -8364,7 +8513,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true + "dev": true, + "optional": true }, "is-stream": { "version": "1.1.0", @@ -8394,7 +8544,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-tar/-/is-tar-1.0.0.tgz", "integrity": "sha1-L2suF5LB9bs2UZrKqdZcDSb+hT0=", - "dev": true + "dev": true, + "optional": true }, "is-typedarray": { "version": "1.0.0", @@ -8415,7 +8566,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true + "dev": true, + "optional": true }, "is-utf8": { "version": "0.2.1", @@ -8427,7 +8579,8 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", - "dev": true + "dev": true, + "optional": true }, "is-windows": { "version": "1.0.2", @@ -8445,7 +8598,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-zip/-/is-zip-1.0.0.tgz", "integrity": "sha1-R7Co/004p2QxzP2ZqOFaTIa6IyU=", - "dev": true + "dev": true, + "optional": true }, "isarray": { "version": "0.0.1", @@ -8689,7 +8843,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -8785,6 +8939,7 @@ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.5" }, @@ -8793,13 +8948,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8815,6 +8972,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -9120,7 +9278,8 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true + "dev": true, + "optional": true }, "lodash.isobject": { "version": "2.4.1", @@ -9317,7 +9476,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", - "dev": true + "dev": true, + "optional": true }, "lpad-align": { "version": "1.1.2", @@ -9753,7 +9913,8 @@ "version": "1.0.0", "resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=", - "dev": true + "dev": true, + "optional": true }, "node.extend": { "version": "1.1.8", @@ -12803,7 +12964,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "dev": true, + "optional": true }, "p-pipe": { "version": "1.2.0", @@ -13514,7 +13676,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true + "dev": true, + "optional": true }, "preserve": { "version": "0.2.0", @@ -13646,6 +13809,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "optional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -13658,6 +13822,7 @@ "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", "dev": true, + "optional": true, "requires": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" @@ -13667,13 +13832,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13689,6 +13856,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -14305,6 +14473,7 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", "dev": true, + "optional": true, "requires": { "commander": "~2.8.1" } @@ -14450,7 +14619,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true + "dev": true, + "optional": true }, "set-value": { "version": "2.0.0", @@ -14955,7 +15125,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", - "dev": true + "dev": true, + "optional": true }, "static-extend": { "version": "0.1.2", @@ -14999,6 +15170,7 @@ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, + "optional": true, "requires": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" @@ -15009,6 +15181,7 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.0.2" } @@ -15017,13 +15190,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15039,6 +15214,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15055,7 +15231,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true + "dev": true, + "optional": true }, "streamroller": { "version": "0.7.0", @@ -15233,6 +15410,7 @@ "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=", "dev": true, + "optional": true, "requires": { "chalk": "^1.0.0", "get-stdin": "^4.0.1", @@ -15246,13 +15424,15 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "dev": true, + "optional": true }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, + "optional": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -15266,6 +15446,7 @@ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", "integrity": "sha1-hHSREZ/MtftDYhfMc39/qtUPYD8=", "dev": true, + "optional": true, "requires": { "is-relative": "^0.1.0" } @@ -15274,13 +15455,15 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=", - "dev": true + "dev": true, + "optional": true }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "dev": true, + "optional": true } } }, @@ -15311,6 +15494,7 @@ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15344,6 +15528,7 @@ "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.3.tgz", "integrity": "sha1-HGYfZnBX9jvLeHWqFDi8FiUlFW4=", "dev": true, + "optional": true, "requires": { "chalk": "^1.0.0" }, @@ -15352,13 +15537,15 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "dev": true, + "optional": true }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, + "optional": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -15371,7 +15558,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "dev": true, + "optional": true } } }, @@ -15433,6 +15621,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, + "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15448,6 +15637,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "dev": true, + "optional": true, "requires": { "once": "^1.4.0" } @@ -15456,13 +15646,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -15472,6 +15664,7 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15487,6 +15680,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15591,6 +15785,7 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", "dev": true, + "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -15615,7 +15810,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz", "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=", - "dev": true + "dev": true, + "optional": true }, "timers-ext": { "version": "0.1.7", @@ -15688,6 +15884,7 @@ "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1" }, @@ -15697,6 +15894,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -15713,7 +15911,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", - "dev": true + "dev": true, + "optional": true }, "to-fast-properties": { "version": "2.0.0", @@ -15792,6 +15991,7 @@ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -16040,7 +16240,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=", - "dev": true + "dev": true, + "optional": true }, "upath": { "version": "1.1.0", @@ -16068,6 +16269,7 @@ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, + "optional": true, "requires": { "prepend-http": "^1.0.1" } @@ -16153,7 +16355,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", - "dev": true + "dev": true, + "optional": true }, "validate-npm-package-license": { "version": "3.0.4", @@ -16198,6 +16401,7 @@ "resolved": "https://registry.npmjs.org/vinyl-assign/-/vinyl-assign-1.2.1.tgz", "integrity": "sha1-TRmIkbVRWRHXcajNnFSApGoHSkU=", "dev": true, + "optional": true, "requires": { "object-assign": "^4.0.1", "readable-stream": "^2.0.0" @@ -16207,19 +16411,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16235,6 +16442,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -16374,6 +16582,7 @@ "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", "integrity": "sha1-0bFPOdLiy0q4xAmPdW/ksWTkc9Q=", "dev": true, + "optional": true, "requires": { "wrap-fn": "^0.1.0" } @@ -16464,6 +16673,7 @@ "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.5.tgz", "integrity": "sha1-8htuQQFv9KfjFyDbxjoJAWvfmEU=", "dev": true, + "optional": true, "requires": { "co": "3.1.0" } @@ -16567,6 +16777,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, + "optional": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html index 817a4f2a94..fae431a11a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-pagination.html @@ -7,7 +7,7 @@ -
  • Date: Mon, 17 Jun 2019 16:33:20 +1000 Subject: [PATCH 051/218] fixes the color of the variant state after publishing and switching to the info tab --- .../components/content/umbcontentnodeinfo.directive.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index e58ff14e21..a6decdc2e5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -328,6 +328,7 @@ isInfoTab = true; loadAuditTrail(); loadRedirectUrls(); + setNodePublishStatus(); } else { isInfoTab = false; } From 3bf43d7ce3ac016482f92c4a4a6d4a399920dbb6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Jun 2019 16:48:35 +1000 Subject: [PATCH 052/218] adds a unit test showing that the SimilarNodeName is broken. --- .../Repositories/SimilarNodeNameTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index 961496ae16..ca47e5928c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -36,6 +36,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsTrue(result > 0, "Expected >0 but was " + result); } + + [Test] public void OrderByTest() { @@ -96,5 +98,21 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(expected, SimilarNodeName.GetUniqueName(names, nodeId, nodeName)); } + + [Test] + public void TestMany() + { + var names = new[] + { + new SimilarNodeName { Id = 1, Name = "Alpha (2)" }, + new SimilarNodeName { Id = 2, Name = "Test" }, + new SimilarNodeName { Id = 3, Name = "Test (1)" }, + new SimilarNodeName { Id = 4, Name = "Test (2)" }, + new SimilarNodeName { Id = 22, Name = "Test (1) (1)" }, + }; + + //fixme - this will yield "Test (2)" which is already in use + Assert.AreEqual("Test (3)", SimilarNodeName.GetUniqueName(names, 0, "Test")); + } } } From b0488d2d94084958171b93881edc63eeae2f4b61 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Jun 2019 16:50:56 +1000 Subject: [PATCH 053/218] fixes showing the correct create date on content creation --- .../components/content/umbcontentnodeinfo.directive.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index a6decdc2e5..e2f5f71781 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -329,6 +329,7 @@ loadAuditTrail(); loadRedirectUrls(); setNodePublishStatus(); + formatDatesToLocal(); } else { isInfoTab = false; } @@ -345,6 +346,7 @@ loadAuditTrail(true); loadRedirectUrls(); setNodePublishStatus(); + formatDatesToLocal(); } updateCurrentUrls(); }); From 94d2a5cbad2548358c88a6d942df5942e66b3c81 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 12 Jun 2019 15:48:19 +0200 Subject: [PATCH 054/218] Bugfix published cache and children --- .../PublishedContent/NuCacheChildrenTests.cs | 83 +++++++++++++++++++ .../Models/PublishedContentBase.cs | 2 +- .../PublishedCache/NuCache/ContentStore.cs | 50 +++++++---- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 7e8a4b18e1..dd656b8019 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -664,6 +664,89 @@ namespace Umbraco.Tests.PublishedContent AssertDocuments(documents, "N1-fr-FR", "N4-fr-FR", null /*11*/, "N12-fr-FR", null /*5*/, "N6-fr-FR"); } + [Test] + public void RemoveTest() + { + Init(GetInvariantKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8", "N7"); + + // notify + _snapshotService.Notify(new[] + { + new ContentCacheRefresher.JsonPayload(3, TreeChangeTypes.Remove), // remove last + new ContentCacheRefresher.JsonPayload(5, TreeChangeTypes.Remove), // remove middle + new ContentCacheRefresher.JsonPayload(9, TreeChangeTypes.Remove), // remove first + }, out _, out _); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N8", "N7"); + + // notify + _snapshotService.Notify(new[] + { + new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.Remove), // remove first + new ContentCacheRefresher.JsonPayload(8, TreeChangeTypes.Remove), // remove + new ContentCacheRefresher.JsonPayload(7, TreeChangeTypes.Remove), // remove + }, out _, out _); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N2"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents); + } + + [Test] + public void UpdateTest() + { + Init(GetInvariantKits()); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8", "N7"); + + // notify + _snapshotService.Notify(new[] + { + new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch), + new ContentCacheRefresher.JsonPayload(2, TreeChangeTypes.RefreshNode), + }, out _, out _); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1", "N2", "N3"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N5", "N6"); + + documents = snapshot.Content.GetById(2).Children().ToArray(); + AssertDocuments(documents, "N9", "N8", "N7"); + } + private void AssertDocuments(IPublishedContent[] documents, params string[] names) { Assert.AreEqual(names.Length, documents.Length); diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index cfc4fd1106..008bf10504 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Models ///
  • /// This base class does which (a) consistently resolves and caches the Url, (b) provides an implementation /// for this[alias], and (c) provides basic content set management. - [DebuggerDisplay("Content Id: {Id}}")] + [DebuggerDisplay("{Content Id: {Id}}")] public abstract class PublishedContentBase : IPublishedContent { #region ContentType diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index f1f8f137fb..e59d332525 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -511,6 +511,11 @@ namespace Umbraco.Web.PublishedCache.NuCache RemoveNodeLocked(existing); AddNodeLocked(kit.Node); } + else + { + // replacing existing, handle siblings + kit.Node.NextSiblingContentId = existing.NextSiblingContentId; + } _xmap[kit.Node.Uid] = kit.Node.Id; } @@ -604,7 +609,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // try to find the content // if it is not there, nothing to do - _contentNodes.TryGetValue(id, out LinkedNode link); // else null + _contentNodes.TryGetValue(id, out var link); // else null if (link?.Value == null) return false; var content = link.Value; @@ -674,6 +679,11 @@ namespace Umbraco.Web.PublishedCache.NuCache var parent = parentLink.Value; + // must have children + if (parent.FirstChildContentId < 0) + throw new Exception("panic: no children"); + + // if first, clone parent + remove first child if (parent.FirstChildContentId == content.Id) { parent = GenCloneLocked(parentLink); @@ -681,11 +691,13 @@ namespace Umbraco.Web.PublishedCache.NuCache } else { + // iterate children until the previous child var link = GetLinkedNode(parent.FirstChildContentId, "first child"); while (link.Value.NextSiblingContentId != content.Id) - link = GetLinkedNode(parent.NextSiblingContentId, "next child"); + link = GetLinkedNode(link.Value.NextSiblingContentId, "next child"); + // clone the previous child and replace next child var prevChild = GenCloneLocked(link); prevChild.NextSiblingContentId = content.NextSiblingContentId; } @@ -732,6 +744,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var parent = parentLink.Value; + // if parent has no children, clone parent + add as first child if (parent.FirstChildContentId < 0) { parent = GenCloneLocked(parentLink); @@ -739,39 +752,45 @@ namespace Umbraco.Web.PublishedCache.NuCache return; } - var prevChildLink = GetLinkedNode(parent.FirstChildContentId, "first child"); - var prevChild = prevChildLink.Value; + // get parent's first child + var childLink = GetLinkedNode(parent.FirstChildContentId, "first child"); + var child = childLink.Value; - if (prevChild.SortOrder > content.SortOrder) + // if first, clone parent + insert as first child + if (child.SortOrder > content.SortOrder) { content.NextSiblingContentId = parent.FirstChildContentId; - parent = GenCloneLocked(parentLink); parent.FirstChildContentId = content.Id; return; } - while (prevChild.NextSiblingContentId > 0) + // else lookup position + while (child.NextSiblingContentId > 0) { - var link = GetLinkedNode(prevChild.NextSiblingContentId, "next child"); - var nextChild = link.Value; + // get next child + var nextChildLink = GetLinkedNode(child.NextSiblingContentId, "next child"); + var nextChild = nextChildLink.Value; + // if here, clone previous + append/insert if (nextChild.SortOrder > content.SortOrder) { content.NextSiblingContentId = nextChild.Id; - prevChild = GenCloneLocked(prevChildLink); - prevChild.NextSiblingContentId = content.Id; + child = GenCloneLocked(childLink); + child.NextSiblingContentId = content.Id; return; } - prevChild = nextChild; - prevChildLink = link; + childLink = nextChildLink; + child = nextChild; } - prevChild = GenCloneLocked(prevChildLink); - prevChild.NextSiblingContentId = content.Id; + // if last, clone previous + append + child = GenCloneLocked(childLink); + child.NextSiblingContentId = content.Id; } + // replaces the root node private void SetRootLocked(ContentNode node) { if (_root.Gen != _liveGen) @@ -784,6 +803,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + // set a node (just the node, not the tree) private void SetValueLocked(ConcurrentDictionary> dict, TKey key, TValue value) where TValue : class { From fecadeec5135ef2f9a93ac4b7b4a303d83e0e2d6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 18 Jun 2019 11:57:36 +1000 Subject: [PATCH 055/218] Fixes issue with nucache and rebuilding and fixes ChildrenForAllCultures to deal with unpublished children --- .../PublishedCache/NuCache/ContentStore.cs | 25 ++++++++++-- .../NuCache/PublishedContent.cs | 39 +++++++++++++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index e59d332525..9eec95c10f 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -647,9 +647,16 @@ namespace Umbraco.Web.PublishedCache.NuCache var id = content.FirstChildContentId; while (id > 0) { - var link = GetLinkedNode(id, "child"); - ClearBranchLocked(link.Value); - id = link.Value.NextSiblingContentId; + if (TryGetLinkedNode(id, out var link)) + { + ClearBranchLocked(link.Value); + id = link.Value.NextSiblingContentId; + } + else + { + // break i guess? + id = 0; + } } } @@ -663,6 +670,18 @@ namespace Umbraco.Web.PublishedCache.NuCache throw new Exception($"panic: failed to get {description} with id={id}"); } + private bool TryGetLinkedNode(int id, out LinkedNode node) + { + if (_contentNodes.TryGetValue(id, out var link) && link.Value != null) + { + node = link; + return true; + } + + node = null; + return false; + } + private LinkedNode GetParentLink(ContentNode content) { _contentNodes.TryGetValue(content.ParentContentId, out var link); // else null diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 20372c074d..2e96513761 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -277,10 +277,43 @@ namespace Umbraco.Web.PublishedCache.NuCache while (id > 0) { var content = getById(publishedSnapshot, IsPreviewing, id); - if (content == null) - throw new Exception($"panic: failed to get content with id={id}"); - yield return content; + if (content != null) + { + yield return content; + } + else + { + //Why were we throwing here? It is perfectly legitimate that a child is not published and when IsPreviewing == false + //this will return null even if the item is in nucache but only with an unpublished flag. + //If we want to be very clear about something being wrong, then perhaps the exception should be thrown from within + //the Func that gets the child where it can validate if there really is nothing there when something is expected? + + //In the meantime, we cannot continue so we should break?... BUT doesn't that mean that if there was a sibling next to this that was published + //that it will now be excluded? + + //Well, in that case this is annoying, so the only thing i can think of is to get the preview version of it to get the + //next child and continue that way? + + if (IsPreviewing) + { + //if we're in preview mode and nothing is returned then something is wrong + throw new Exception($"panic: failed to get content with id={id}"); + } + else + { + //get the preview version since this item might not be published + content = getById(publishedSnapshot, true, id); + if (content == null) + { + //if we're in preview mode and nothing is returned then something is wrong + throw new Exception($"panic: failed to get content with id={id}"); + } + + //now we can continue with the next sibling id, but we aren't going to return this content item because it's not published + //and we're not previewing. + } + } id = UnwrapIPublishedContent(content)._contentNode.NextSiblingContentId; } From cb428bb288a4b786e474d86fd55017f2a64fd7a9 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 18 Jun 2019 08:11:18 +0200 Subject: [PATCH 056/218] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1447: Catch the SoapException which happends when a timeout occurs on the server. Also change such that this check is only executed for super admins. --- src/Umbraco.Web/Editors/UpdateCheckController.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index cd11382d13..5193b08edc 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Editors { var updChkCookie = Request.Headers.GetCookies("UMB_UPDCHK").FirstOrDefault(); var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : ""; - if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsAdmin()) + if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsSuper()) { try { @@ -37,6 +37,11 @@ namespace Umbraco.Web.Editors //this occurs if the server is down or cannot be reached return null; } + catch (System.Web.Services.Protocols.SoapException) + { + //this occurs if the server has a timeout + return null; + } } return null; } From 067af64dfff486690aca612c4f83c9e954dced4a Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 18 Jun 2019 09:46:29 +0200 Subject: [PATCH 057/218] Cleanup ChildrenForAllCultures --- .../NuCache/PublishedContent.cs | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 2e96513761..cb7acdc164 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -121,7 +121,6 @@ namespace Umbraco.Web.PublishedCache.NuCache default: throw new Exception("panic: invalid item type"); } - } #endregion @@ -276,6 +275,7 @@ namespace Umbraco.Web.PublishedCache.NuCache while (id > 0) { + // is IsPreviewing is false, then this can return null var content = getById(publishedSnapshot, IsPreviewing, id); if (content != null) @@ -284,35 +284,15 @@ namespace Umbraco.Web.PublishedCache.NuCache } else { - //Why were we throwing here? It is perfectly legitimate that a child is not published and when IsPreviewing == false - //this will return null even if the item is in nucache but only with an unpublished flag. - //If we want to be very clear about something being wrong, then perhaps the exception should be thrown from within - //the Func that gets the child where it can validate if there really is nothing there when something is expected? - - //In the meantime, we cannot continue so we should break?... BUT doesn't that mean that if there was a sibling next to this that was published - //that it will now be excluded? - - //Well, in that case this is annoying, so the only thing i can think of is to get the preview version of it to get the - //next child and continue that way? - + // but if IsPreviewing is true, we should have a child if (IsPreviewing) - { - //if we're in preview mode and nothing is returned then something is wrong throw new Exception($"panic: failed to get content with id={id}"); - } - else - { - //get the preview version since this item might not be published - content = getById(publishedSnapshot, true, id); - if (content == null) - { - //if we're in preview mode and nothing is returned then something is wrong - throw new Exception($"panic: failed to get content with id={id}"); - } - //now we can continue with the next sibling id, but we aren't going to return this content item because it's not published - //and we're not previewing. - } + // if IsPreviewing is false, get the unpublished child nevertheless + // we need it to keep enumerating children! but we don't return it + content = getById(publishedSnapshot, true, id); + if (content == null) + throw new Exception($"panic: failed to get content with id={id}"); } id = UnwrapIPublishedContent(content)._contentNode.NextSiblingContentId; From 62e6168e036c8bbd3a36cf328d097229c87039a6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 18 Jun 2019 11:36:53 +0200 Subject: [PATCH 058/218] Revert ContentStore changes - have to find the bug now --- .../PublishedCache/NuCache/ContentStore.cs | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 9eec95c10f..e59d332525 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -647,16 +647,9 @@ namespace Umbraco.Web.PublishedCache.NuCache var id = content.FirstChildContentId; while (id > 0) { - if (TryGetLinkedNode(id, out var link)) - { - ClearBranchLocked(link.Value); - id = link.Value.NextSiblingContentId; - } - else - { - // break i guess? - id = 0; - } + var link = GetLinkedNode(id, "child"); + ClearBranchLocked(link.Value); + id = link.Value.NextSiblingContentId; } } @@ -670,18 +663,6 @@ namespace Umbraco.Web.PublishedCache.NuCache throw new Exception($"panic: failed to get {description} with id={id}"); } - private bool TryGetLinkedNode(int id, out LinkedNode node) - { - if (_contentNodes.TryGetValue(id, out var link) && link.Value != null) - { - node = link; - return true; - } - - node = null; - return false; - } - private LinkedNode GetParentLink(ContentNode content) { _contentNodes.TryGetValue(content.ParentContentId, out var link); // else null From 9614740410e167799be50bba4c030fb7bddff29a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 18 Jun 2019 11:44:56 +0200 Subject: [PATCH 059/218] Added timeout for 2 seconds. Dont make sense to wait longer for this --- src/Umbraco.Web/Editors/UpdateCheckController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index 5193b08edc..b197fe8926 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -24,7 +24,8 @@ namespace Umbraco.Web.Editors { try { - var check = new org.umbraco.update.CheckForUpgrade(); + var check = new org.umbraco.update.CheckForUpgrade { Timeout = 2000 }; + var result = check.CheckUpgrade(UmbracoVersion.Current.Major, UmbracoVersion.Current.Minor, UmbracoVersion.Current.Build, From 832dc7f4a5cf70790253f30b395bb67e811c2351 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 18 Jun 2019 18:44:22 +0200 Subject: [PATCH 060/218] Changed superadmin back to admin again --- src/Umbraco.Web/Editors/UpdateCheckController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/UpdateCheckController.cs b/src/Umbraco.Web/Editors/UpdateCheckController.cs index b197fe8926..132526576b 100644 --- a/src/Umbraco.Web/Editors/UpdateCheckController.cs +++ b/src/Umbraco.Web/Editors/UpdateCheckController.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Editors { var updChkCookie = Request.Headers.GetCookies("UMB_UPDCHK").FirstOrDefault(); var updateCheckCookie = updChkCookie != null ? updChkCookie["UMB_UPDCHK"].Value : ""; - if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsSuper()) + if (GlobalSettings.VersionCheckPeriod > 0 && string.IsNullOrEmpty(updateCheckCookie) && Security.CurrentUser.IsAdmin()) { try { From 76cbfdb7874530fd906db5383e73a68db9eaaea5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Jun 2019 12:31:36 +1000 Subject: [PATCH 061/218] ignores a failing test, will need a separate task for that. --- .../Persistence/Repositories/SimilarNodeNameTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs index ca47e5928c..582e5a4815 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/SimilarNodeNameTests.cs @@ -100,6 +100,7 @@ namespace Umbraco.Tests.Persistence.Repositories } [Test] + [Explicit("This test fails! We need to fix up the logic")] public void TestMany() { var names = new[] From 5c2b0647f3feaf23311bc26876a183bbb348bad5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Jun 2019 16:42:04 +1000 Subject: [PATCH 062/218] Gets the feature in v7 based on #2682 that wasn't merged upwards to v8 and found some issues along the way like not filtering the mini list view properly along with not returning the content type alias in the entity controller --- .../components/tree/umbtree.directive.js | 30 ++++++---- .../src/common/services/tree.service.js | 60 ++++++++++++------- .../treepicker/treepicker.controller.js | 14 +++++ .../treepicker/treepicker.html | 2 +- .../Models/Mapping/EntityMapDefinition.cs | 4 +- 5 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 7055d1a746..cfc1cfbfd3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -38,7 +38,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use treeOptionsClick: [], treeNodeAltSelect: [] }; - + //this is the API exposed by this directive, for either hosting controllers or for other directives vm.callbacks = { treeNodeExpanded: function (f) { @@ -82,7 +82,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use // since it saves on data retreival and DOM processing. // TODO: This isn't used!? var lastSection = ""; - + /** Helper function to emit tree events */ function emitEvent(eventName, args) { if (registeredCallbacks[eventName] && angular.isArray(registeredCallbacks[eventName])) { @@ -119,7 +119,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use $scope.cachekey = args.cacheKey; } } - + return loadTree(); } @@ -148,7 +148,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use if (!args.path) { throw "args.path cannot be null"; } - + if (angular.isString(args.path)) { args.path = args.path.replace('"', '').split(','); } @@ -172,8 +172,16 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use emitEvent("treeSynced", { node: data, activate: args.activate }); return $q.when({ node: data, activate: args.activate }); + }, function (data) { + return $q.reject(data); + }, function (data) { + //on notification + if (data.type === "treeNodeExpanded") { + //raise the event + emitEvent("treeNodeExpanded", { tree: $scope.tree, node: data.node, children: data.children }); + } }); - + } /** This will check the section tree loaded and return all actual root nodes based on a tree type (non group nodes, non section groups) */ @@ -201,7 +209,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node function loadActiveTree(treeAlias) { - + if (!$scope.tree) { throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; } @@ -233,7 +241,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use function loadTree() { if (!$scope.loading && $scope.section) { $scope.loading = true; - + //default args var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false }; @@ -279,7 +287,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use if (forceReload || (node.hasChildren && node.children.length === 0)) { //get the children from the tree service return treeService.loadNodeChildren({ node: node, section: $scope.section, isDialog: $scope.isdialog }) - .then(function(data) { + .then(function (data) { //emit expanded event emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: data }); @@ -302,7 +310,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use // TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time // it would be better if we could cache the processing. The problem is that some of these things are dynamic. - + var css = []; if (node.cssClasses) { _.each(node.cssClasses, function (c) { @@ -322,7 +330,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use }; /* helper to force reloading children of a tree node */ - $scope.loadChildren = function(node, forceReload) { + $scope.loadChildren = function (node, forceReload) { return loadChildren(node, forceReload); }; @@ -359,7 +367,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use $scope.altSelect = function (n, ev) { emitEvent("treeNodeAltSelect", { element: $element, tree: $scope.tree, node: n, event: ev }); }; - + //call the onInit method, if the result is a promise then load the tree after that resolves (if it's not a promise this will just resolve automatically). //NOTE: The promise cannot be rejected, else the tree won't be loaded and we'll get exceptions if some API calls syncTree or similar. $q.when($scope.onInit(), function (args) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index d61d1c3ba1..3e60b09ad9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -41,7 +41,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return { /** Internal method to return the tree cache */ - _getTreeCache: function() { + _getTreeCache: function () { return treeCache; }, @@ -97,7 +97,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } //create a method outside of the loop to return the parent - otherwise jshint blows up - var funcParent = function() { + var funcParent = function () { return parentNode; }; @@ -168,7 +168,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * * @param {String} treeAlias The tree alias to check */ - getTreePackageFolder: function(treeAlias) { + getTreePackageFolder: function (treeAlias) { //we determine this based on the server variables if (Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.trees && @@ -220,7 +220,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var self = this; this.clearCache({ cacheKey: args.cacheKey, - filter: function(cc) { + filter: function (cc) { //get the new parent node from the tree cache var parent = self.getDescendantNode(cc.root, args.childrenOf); if (parent) { @@ -288,7 +288,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @param {object} args.node The tree node * @param {object} args.section The current section */ - loadNodeChildren: function(args) { + loadNodeChildren: function (args) { if (!args) { throw "No args object defined for loadNodeChildren"; } @@ -303,7 +303,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS args.node.loading = true; return this.getChildren(args) - .then(function(data) { + .then(function (data) { //set state to done and expand (only if there actually are children!) args.node.loading = false; @@ -320,10 +320,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return $q.when(data); - }, function(reason) { + }, function (reason) { //in case of error, emit event - eventsService.emit("treeService.treeNodeLoadError", {error: reason } ); + eventsService.emit("treeService.treeNodeLoadError", { error: reason }); //stop show the loading indicator args.node.loading = false; @@ -346,7 +346,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * Removes a given node from the tree * @param {object} treeNode the node to remove */ - removeNode: function(treeNode) { + removeNode: function (treeNode) { if (!angular.isFunction(treeNode.parent)) { return; } @@ -359,7 +359,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS parent.children.splice(parent.children.indexOf(treeNode), 1); parent.hasChildren = parent.children.length !== 0; - + //Notify that the node has been removed eventsService.emit("treeService.removeNode", { node: treeNode }); }, @@ -374,7 +374,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * Removes all child nodes from a given tree node * @param {object} treeNode the node to remove children from */ - removeChildNodes : function(treeNode) { + removeChildNodes: function (treeNode) { treeNode.expanded = false; treeNode.children = []; treeNode.hasChildren = false; @@ -413,7 +413,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @param {int} id id of descendant node * @param {string} treeAlias - optional tree alias, if fetching descendant node from a child of a listview document */ - getDescendantNode: function(treeNode, id, treeAlias) { + getDescendantNode: function (treeNode, id, treeAlias) { //validate if it is a section container since we'll need a treeAlias if it is one if (treeNode.isContainer === true && !treeAlias) { @@ -432,7 +432,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var root = getTreeRoot(tn.children[c]); //only return if we found the root in this child, otherwise continue. - if(root){ + if (root) { return root; } } @@ -531,7 +531,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node * @param {object} treeNode to retrive tree alias from */ - getTreeAlias : function(treeNode) { + getTreeAlias: function (treeNode) { var root = this.getTreeRoot(treeNode); if (root) { return root.metaData["treeAlias"]; @@ -570,7 +570,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var self = this; return treeResource.loadApplication(args) - .then(function(data) { + .then(function (data) { //this will be called once the tree app data has loaded var result = { name: data.name, @@ -624,7 +624,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } return treeResource.loadMenu(args.treeNode) - .then(function(data) { + .then(function (data) { //need to convert the icons to new ones for (var i = 0; i < data.length; i++) { data[i].cssclass = iconHelper.convertFromLegacyIcon(data[i].cssclass); @@ -677,7 +677,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * Re-loads the single node from the server * @param {object} node Tree node to reload */ - reloadNode: function(node) { + reloadNode: function (node) { if (!node) { throw "node cannot be null"; } @@ -691,10 +691,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //set the node to loading node.loading = true; - return this.getChildren({ node: node.parent(), section: node.section }).then(function(data) { + return this.getChildren({ node: node.parent(), section: node.section }).then(function (data) { //ok, now that we have the children, find the node we're reloading - var found = _.find(data, function(item) { + var found = _.find(data, function (item) { return item.id === node.id; }); if (found) { @@ -720,7 +720,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS else { return $q.reject(); } - }, function() { + }, function () { return $q.reject(); }); }, @@ -735,7 +735,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * This will return the current node's path by walking up the tree * @param {object} node Tree node to retrieve path for */ - getPath: function(node) { + getPath: function (node) { if (!node) { throw "node cannot be null"; } @@ -760,7 +760,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return reversePath.reverse(); }, - syncTree: function(args) { + syncTree: function (args) { if (!args) { throw "No args object defined for syncTree"; @@ -800,6 +800,8 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } } + var deferred = $q.defer(); + //now that we have the first id to lookup, we can start the process var self = this; @@ -831,6 +833,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS else { //couldn't find it in the return self.loadNodeChildren({ node: node, section: node.section }).then(function (children) { + + //send back some progress to allow the caller to deal with expanded nodes + deferred.notify({ type: "treeNodeExpanded", node: node, children: children }) + //ok, got the children, let's find it var found = self.getChildNode(node, args.path[currPathIndex]); if (found) { @@ -858,8 +864,16 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS }; //start - return doSync(); + var wrappedPromise = doSync(); + //then wrap it + wrappedPromise.then(function (args) { + deferred.resolve(args); + }, function (args) { + deferred.reject(args); + }); + + return deferred.promise; } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 915abf62b0..7ba6e71f0c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -59,6 +59,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.submit = submit; vm.close = close; + var currentNode = $scope.model.currentNode; + function initDialogTree() { vm.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler); // TODO: Also deal with unexpanding!! @@ -160,6 +162,12 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", } } } + + vm.filter = { + filterAdvanced: $scope.model.filterAdvanced, + filterExclude: $scope.model.filterExclude, + filter: $scope.model.filter + }; } /** @@ -256,6 +264,12 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.hasItems = args.tree.root.children.length > 0; tree = args.tree; + + var nodeHasPath = currentNode && currentNode.path; + var startNodeNotDefined = !vm.startNodeId; + if (startNodeNotDefined && nodeHasPath) { + vm.dialogTreeApi.syncTree({ path: currentNode.path, activate: false }); + } } //wires up selection diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index c592b4ec3b..57ee805ba0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -74,7 +74,7 @@ start-node-id="vm.startNodeId" on-select="vm.selectListViewNode(node)" on-close="vm.closeMiniListView()" - entity-type-filter="filter"> + entity-type-filter="vm.filter"> diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index 2598523bd5..d5bc6adee9 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -45,10 +45,10 @@ namespace Umbraco.Web.Models.Mapping if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) target.Icon = "icon-user"; - if (source.NodeObjectType == Constants.ObjectTypes.Media && source is IContentEntitySlim contentSlim) + if (source is IContentEntitySlim contentSlim) source.AdditionalData["ContentTypeAlias"] = contentSlim.ContentTypeAlias; - if (source.NodeObjectType == Constants.ObjectTypes.Media && source is IMediaEntitySlim mediaSlim) + if (source is IMediaEntitySlim mediaSlim) source.AdditionalData["MediaPath"] = mediaSlim.MediaPath; // NOTE: we're mapping the objects in AdditionalData by object reference here. From 1dd2c1cb915a9b807ccb9425d0e61ac0d4a1bf6d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 19 Jun 2019 14:30:44 +0200 Subject: [PATCH 063/218] Revert "Revert "U4 10147 - Bypass User Security option in pickers (#2441)"" This reverts commit 994c6eed --- .../tree/umbtreesearchbox.directive.js | 7 + .../src/common/resources/content.resource.js | 1577 +++++++++-------- .../src/common/resources/entity.resource.js | 49 +- .../src/common/resources/media.resource.js | 6 +- .../src/common/services/search.service.js | 22 +- .../overlays/contentpicker/contentpicker.html | 3 +- .../linkpicker/linkpicker.controller.js | 301 ++-- .../overlays/linkpicker/linkpicker.html | 2 + .../mediaPicker/mediapicker.controller.js | 55 +- .../treepicker/treepicker.controller.js | 1047 +++++------ .../overlays/treepicker/treepicker.html | 1 + .../contentpicker/contentpicker.controller.js | 4 +- .../grid/editors/media.controller.js | 17 +- .../grid/editors/rte.controller.js | 17 +- .../mediapicker/mediapicker.controller.js | 16 +- .../multiurlpicker.controller.js | 3 +- .../relatedlinks/relatedlinks.controller.js | 2 + .../propertyeditors/rte/rte.controller.js | 15 +- .../propertyeditors/rte/rte.prevalues.html | 1 + src/Umbraco.Web/Editors/ContentController.cs | 12 +- src/Umbraco.Web/Editors/EntityController.cs | 106 +- src/Umbraco.Web/Editors/MediaController.cs | 14 +- .../ContentPicker2PropertyEditor.cs | 14 +- .../PropertyEditors/GridPropertyEditor.cs | 3 + .../MediaPicker2PropertyEditor.cs | 9 +- .../MultiNodeTreePicker2PropertyEditor.cs | 10 +- .../MultiUrlPickerPropertyEditor.cs | 7 + .../RelatedLinks2PropertyEditor.cs | 6 +- .../PropertyEditors/RichTextPreValueEditor.cs | 8 + src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 34 +- .../Trees/ContentTreeControllerBase.cs | 12 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 10 + .../Trees/TreeQueryStringParameters.cs | 3 +- ...EnsureUserPermissionForContentAttribute.cs | 15 +- .../FilterAllowedOutgoingMediaAttribute.cs | 9 +- 35 files changed, 1861 insertions(+), 1556 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index 4ba4cf96bb..b81e62a66b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,6 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", + ignoreUserStartNodes: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -34,6 +35,7 @@ function treeSearchBox(localizationService, searchService, $q) { scope.showSearch = "false"; } + //used to cancel any request in progress if another one needs to take it's place var canceler = null; @@ -60,6 +62,11 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } + //append ignoreUserStartNodes value if there is one + if (scope.ignoreUserStartNodes) { + searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes; + } + searcher(searchArgs).then(function (data) { scope.searchCallback(data); //set back to null so it can be re-created diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index b695809eaa..77e587bcc6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -1,782 +1,795 @@ -/** - * @ngdoc service - * @name umbraco.resources.contentResource - * @description Handles all transactions of content data - * from the angular application to the Umbraco database, using the Content WebApi controller - * - * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. - * - * @requires $q - * @requires $http - * @requires umbDataFormatter - * @requires umbRequestHelper - * - * ##usage - * To use, simply inject the contentResource into any controller or service that needs it, and make - * sure the umbraco.resources module is accesible - which it should be by default. - * - *
    -  *    contentResource.getById(1234)
    -  *          .then(function(data) {
    -  *              $scope.content = data;
    -  *          });    
    -  * 
    - **/ - -function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files, restApiUrl) { - return umbRequestHelper.postSaveContent({ - restApiUrl: restApiUrl, - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } - - return { - - - savePermissions: function (saveModel) { - if (!saveModel) { - throw "saveModel cannot be null"; - } - if (!saveModel.contentId) { - throw "saveModel.contentId cannot be null"; - } - if (!saveModel.permissions) { - throw "saveModel.permissions cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), - saveModel), - 'Failed to save permissions'); - }, - - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
    -          * var ids = [123,34533,2334,23434];
    -          * contentResource.sort({ parentId: 1244, sortedIds: ids })
    -          *    .then(function() {
    -          *        $scope.complete = true;
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
    -          * contentResource.move({ parentId: 1244, id: 123 })
    -          *    .then(function() {
    -          *        alert("node was moved");
    -          *    }, function(err){
    -          *      alert("node didnt move:" + err.data.Message); 
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
    -          * contentResource.copy({ parentId: 1244, id: 123 })
    -          *    .then(function() {
    -          *        alert("node was copied");
    -          *    }, function(err){
    -          *      alert("node wasnt copy:" + err.data.Message); 
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
    -          * contentResource.unPublish(1234)
    -          *    .then(function() {
    -          *        alert("node was unpulished");
    -          *    }, function(err){
    -          *      alert("node wasnt unpublished:" + err.data.Message); 
    -          *    });
    -          * 
    - * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ - unPublish: function (id) { - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
    -          * contentResource.emptyRecycleBin()
    -          *    .then(function() {
    -          *        alert('its empty!');
    -          *    });
    -          * 
    - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
    -          * contentResource.deleteById(1234)
    -          *    .then(function() {
    -          *        alert('its gone!');
    -          *    });
    -          * 
    - * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - deleteBlueprint: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteBlueprint", - [{ id: id }])), - 'Failed to delete blueprint ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
    -          * contentResource.getById(1234)
    -          *    .then(function(content) {
    -          *        var myDoc = content; 
    -          *        alert('its here!');
    -          *    });
    -          * 
    - * - * @param {Int} id id of content item to return - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - getBlueprintById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetBlueprintById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - getNotifySettingsById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNotificationOptions", - [{ contentId: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - setNotifySettingsById: function (id, options) { - if (!id) { - throw "contentId cannot be null"; - } - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostNotificationOptions", - { contentId: id, notifyOptions: options })), - 'Failed to set notify settings for content id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
    -          * contentResource.getByIds( [1234,2526,28262])
    -          *    .then(function(contentArray) {
    -          *        var myDoc = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
    -          * contentResource.getScaffold(1234, 'homepage')
    -          *    .then(function(scaffold) {
    -          *        var myDoc = scaffold;
    -          *        myDoc.name = "My new document"; 
    -          *
    -          *        contentResource.publish(myDoc, true)
    -          *            .then(function(content){
    -          *                alert("Retrieved, updated and published again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); - }, - - getBlueprintScaffold: function (parentId, blueprintId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId}])), - 'Failed to retrieve blueprint for id ' + blueprintId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
    -          * contentResource.getNiceUrl(id)
    -          *    .then(function(url) {
    -          *        alert('its here!');
    -          *    });
    -          * 
    - * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ - getNiceUrl: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl", [{ id: id }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
    -          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
    -          *    .then(function(contentArray) {
    -          *        var children = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { - - var defaults = { - includeProperties: [], - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; - } - if (angular.isString(v)) { - return v === "true"; - } - if (typeof v === "boolean") { - return v; - } - return false; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - { - id: parentId, - includeProperties: _.pluck(options.includeProperties, 'alias').join(","), - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - orderBySystemField: toBool(options.orderBySystemField), - filter: options.filter - })), - 'Failed to retrieve children for content item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
    -          * contentResource.hasPermission('p',1234)
    -          *    .then(function() {
    -          *        alert('You are allowed to publish this item');
    -          *    });
    -          * 
    - * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - checkPermission: function (permission, id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission }, { nodeId: id }])), - 'Failed to check permission for item ' + id); - }, - - getDetailedPermissions: function (contentId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetDetailedPermissions", { contentId: contentId })), - 'Failed to retrieve permissions for content item ' + contentId); - }, - - getPermissions: function (nodeIds) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetPermissions"), - nodeIds), - 'Failed to get permissions'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
    -          * contentResource.getById(1234)
    -          *    .then(function(content) {
    -          *          content.name = "I want a new name!";
    -          *          contentResource.save(content, false)
    -          *            .then(function(content){
    -          *                alert("Retrieved, updated and saved again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - save: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); - }, - - saveBlueprint: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSaveBlueprint"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
    -          * contentResource.getById(1234)
    -          *    .then(function(content) {
    -          *          content.name = "I want a new name, and be published!";
    -          *          contentResource.publish(content, false)
    -          *            .then(function(content){
    -          *                alert("Retrieved, updated and published again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - publish: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
    -          * contentResource.getById(1234)
    -          *    .then(function(content) {
    -          *          content.name = "I want a new name, and be published!";
    -          *          contentResource.sendToPublish(content, false)
    -          *            .then(function(content){
    -          *                alert("Retrieved, updated and notication send off");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - sendToPublish: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
    -          * contentResource.publishById(1234)
    -          *    .then(function(content) {
    -          *        alert("published");
    -          *    });
    -          * 
    - * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ - publishById: function (id) { - - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); - - }, - - createBlueprintFromContent: function (contentId, name) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", { - contentId: contentId, name: name - }) - ), - "Failed to create blueprint from content with id " + contentId - ); - } - - - }; -} - -angular.module('umbraco.resources').factory('contentResource', contentResource); +/** + * @ngdoc service + * @name umbraco.resources.contentResource + * @description Handles all transactions of content data + * from the angular application to the Umbraco database, using the Content WebApi controller + * + * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. + * + * @requires $q + * @requires $http + * @requires umbDataFormatter + * @requires umbRequestHelper + * + * ##usage + * To use, simply inject the contentResource into any controller or service that needs it, and make + * sure the umbraco.resources module is accesible - which it should be by default. + * + *
    +  *    contentResource.getById(1234)
    +  *          .then(function(data) {
    +  *              $scope.content = data;
    +  *          });    
    +  * 
    + **/ + +function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { + + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files, restApiUrl) { + return umbRequestHelper.postSaveContent({ + restApiUrl: restApiUrl, + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); + } + + return { + + + savePermissions: function (saveModel) { + if (!saveModel) { + throw "saveModel cannot be null"; + } + if (!saveModel.contentId) { + throw "saveModel.contentId cannot be null"; + } + if (!saveModel.permissions) { + throw "saveModel.permissions cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), + saveModel), + 'Failed to save permissions'); + }, + + + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for content recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
    +          * var ids = [123,34533,2334,23434];
    +          * contentResource.sort({ parentId: 1244, sortedIds: ids })
    +          *    .then(function() {
    +          *        $scope.complete = true;
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +          * contentResource.move({ parentId: 1244, id: 123 })
    +          *    .then(function() {
    +          *        alert("node was moved");
    +          *    }, function(err){
    +          *      alert("node didnt move:" + err.data.Message); 
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
    +          * contentResource.copy({ parentId: 1244, id: 123 })
    +          *    .then(function() {
    +          *        alert("node was copied");
    +          *    }, function(err){
    +          *      alert("node wasnt copy:" + err.data.Message); 
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), + args), + 'Failed to copy content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
    +          * contentResource.unPublish(1234)
    +          *    .then(function() {
    +          *        alert("node was unpulished");
    +          *    }, function(err){
    +          *      alert("node wasnt unpublished:" + err.data.Message); 
    +          *    });
    +          * 
    + * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ + unPublish: function (id) { + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + [{ id: id }])), + 'Failed to publish content with id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
    +          * contentResource.emptyRecycleBin()
    +          *    .then(function() {
    +          *        alert('its empty!');
    +          *    });
    +          * 
    + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
    +          * contentResource.deleteById(1234)
    +          *    .then(function() {
    +          *        alert('its gone!');
    +          *    });
    +          * 
    + * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, + + deleteBlueprint: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteBlueprint", + [{ id: id }])), + 'Failed to delete blueprint ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
    +          * contentResource.getById(1234)
    +          *    .then(function(content) {
    +          *        var myDoc = content; 
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id id of content item to return + * @param {Object} options optional options object + * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id, options) { + var defaults = { + ignoreUserStartNodes: false + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), + 'Failed to retrieve data for content id ' + id); + }, + + getBlueprintById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetBlueprintById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + getNotifySettingsById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNotificationOptions", + [{ contentId: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + setNotifySettingsById: function (id, options) { + if (!id) { + throw "contentId cannot be null"; + } + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostNotificationOptions", + { contentId: id, notifyOptions: options })), + 'Failed to set notify settings for content id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
    +          * contentResource.getByIds( [1234,2526,28262])
    +          *    .then(function(contentArray) {
    +          *        var myDoc = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids'); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
    +          * contentResource.getScaffold(1234, 'homepage')
    +          *    .then(function(scaffold) {
    +          *        var myDoc = scaffold;
    +          *        myDoc.name = "My new document"; 
    +          *
    +          *        contentResource.publish(myDoc, true)
    +          *            .then(function(content){
    +          *                alert("Retrieved, updated and published again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); + }, + + getBlueprintScaffold: function (parentId, blueprintId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ blueprintId: blueprintId }, { parentId: parentId}])), + 'Failed to retrieve blueprint for id ' + blueprintId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
    +          * contentResource.getNiceUrl(id)
    +          *    .then(function(url) {
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ + getNiceUrl: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
    +          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + includeProperties: [], + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + { + id: parentId, + includeProperties: _.pluck(options.includeProperties, 'alias').join(","), + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + orderBySystemField: toBool(options.orderBySystemField), + filter: options.filter + })), + 'Failed to retrieve children for content item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
    +          * contentResource.hasPermission('p',1234)
    +          *    .then(function() {
    +          *        alert('You are allowed to publish this item');
    +          *    });
    +          * 
    + * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + checkPermission: function (permission, id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); + }, + + getDetailedPermissions: function (contentId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetDetailedPermissions", { contentId: contentId })), + 'Failed to retrieve permissions for content item ' + contentId); + }, + + getPermissions: function (nodeIds) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetPermissions"), + nodeIds), + 'Failed to get permissions'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
    +          * contentResource.getById(1234)
    +          *    .then(function(content) {
    +          *          content.name = "I want a new name!";
    +          *          contentResource.save(content, false)
    +          *            .then(function(content){
    +          *                alert("Retrieved, updated and saved again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + save: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + }, + + saveBlueprint: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSaveBlueprint"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
    +          * contentResource.getById(1234)
    +          *    .then(function(content) {
    +          *          content.name = "I want a new name, and be published!";
    +          *          contentResource.publish(content, false)
    +          *            .then(function(content){
    +          *                alert("Retrieved, updated and published again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + publish: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
    +          * contentResource.getById(1234)
    +          *    .then(function(content) {
    +          *          content.name = "I want a new name, and be published!";
    +          *          contentResource.sendToPublish(content, false)
    +          *            .then(function(content){
    +          *                alert("Retrieved, updated and notication send off");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + sendToPublish: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
    +          * contentResource.publishById(1234)
    +          *    .then(function(content) {
    +          *        alert("published");
    +          *    });
    +          * 
    + * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ + publishById: function (id) { + + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); + + }, + + createBlueprintFromContent: function (contentId, name) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", { + contentId: contentId, name: name + }) + ), + "Failed to create blueprint from content with id " + contentId + ); + } + + + }; +} + +angular.module('umbraco.resources').factory('contentResource', contentResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 72f8ad5539..4875491dc6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -292,14 +292,29 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type) { + getAncestors: function (id, type, options) { + var defaults = { + ignoreUserStartNodes: false + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [{id: id}, {type: type}])), - 'Failed to retrieve ancestor data for id ' + id); + [ + { id: id }, + { type: type }, + { ignoreUserStartNodes: options.ignoreUserStartNodes } + ])), + 'Failed to retrieve ancestor data for id ' + id); }, /** @@ -431,7 +446,8 @@ function entityResource($q, $http, umbRequestHelper) { pageNumber: 1, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + ignoreUserStartNodes: false }; if (options === undefined) { options = {}; @@ -460,7 +476,8 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) + filter: encodeURIComponent(options.filter), + ignoreUserStartNodes: options.ignoreUserStartNodes } )), 'Failed to retrieve child data for id ' + parentId); @@ -488,12 +505,19 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, searchFrom, canceler) { + search: function (query, type, options, canceler) { - var args = [{ query: query }, { type: type }]; - if (searchFrom) { - args.push({ searchFrom: searchFrom }); + var defaults = { + searchFrom: null, + ignoreUserStartNodes: false + }; + if (options === undefined) { + options = {}; } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; var httpConfig = {}; if (canceler) { @@ -505,7 +529,12 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "Search", - args), + { + query: query, + type: type, + searchFrom: options.searchFrom, + ignoreUserStartNodes: options.ignoreUserStartNodes + }), httpConfig), 'Failed to retrieve entity data for query ' + query); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 8c27f20aea..e968913047 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -329,7 +329,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - orderBySystemField: true + orderBySystemField: true, + ignoreUserStartNodes: false }; if (options === undefined) { options = {}; @@ -372,7 +373,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { { orderBy: options.orderBy }, { orderDirection: options.orderDirection }, { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } + { filter: options.filter }, + { ignoreUserStartNodes: options.ignoreUserStartNodes } ])), 'Failed to retrieve children for media item ' + parentId); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 8738c1011e..0d00678282 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -42,7 +42,11 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { + var options = { + searchFrom: args.searchFrom + } + + return entityResource.search(args.term, "Member", options).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMemberResult(item); }); @@ -67,7 +71,12 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { + var options = { + searchFrom: args.searchFrom, + ignoreUserStartNodes: args.ignoreUserStartNodes + } + + return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -92,7 +101,12 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { + var options = { + searchFrom: args.searchFrom, + ignoreUserStartNodes: args.ignoreUserStartNodes + } + + return entityResource.search(args.term, "Media", options).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); @@ -157,4 +171,4 @@ angular.module('umbraco.services') var currentSection = sectionAlias; } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html index 43eab532d4..4391e50c28 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html @@ -9,6 +9,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="content"> @@ -45,4 +46,4 @@ on-close="closeMiniListView()"> - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index fcce34621b..79b9362d3f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -1,57 +1,58 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { - var dialogOptions = $scope.model; + function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + var dialogOptions = $scope.model; - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); - } + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); + } - $scope.dialogTreeEventHandler = $({}); - $scope.model.target = {}; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - }; + $scope.dialogTreeEventHandler = $({}); + $scope.model.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + results: [], + selectedSearchResults: [] + }; + $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; + $scope.showTarget = $scope.model.hideTarget !== true; - $scope.showTarget = $scope.model.hideTarget !== true; + if (dialogOptions.currentTarget) { + // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target + $scope.model.target = angular.copy(dialogOptions.currentTarget); + //if we have a node ID, we fetch the current node to build the form data + if ($scope.model.target.id || $scope.model.target.udi) { - if (dialogOptions.currentTarget) { - // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target - $scope.model.target = angular.copy(dialogOptions.currentTarget); - //if we have a node ID, we fetch the current node to build the form data - if ($scope.model.target.id || $scope.model.target.udi) { + //will be either a udi or an int + var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; - //will be either a udi or an int - var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; + // is it a content link? + if (!$scope.model.target.isMedia) { + // get the content path + entityResource.getPath(id, "Document").then(function (path) { + //now sync the tree to this path + $scope.dialogTreeEventHandler.syncTree({ + path: path, + tree: "content" + }); + }); - // is it a content link? - if (!$scope.model.target.isMedia) { - // get the content path - entityResource.getPath(id, "Document").then(function(path) { - //now sync the tree to this path - $scope.dialogTreeEventHandler.syncTree({ - path: path, - tree: "content" - }); - }); - - // get the content properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - }); - } - } else if ($scope.model.target.url.length) { - // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs + // if a link exists, get the properties to build the anchor name list + contentResource.getById(id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + $scope.model.target.url = resp.urls[0]; + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + }); + } + } else if ($scope.model.target.url.length) { + // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/); if (indexOfAnchor > -1) { @@ -60,124 +61,132 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", // then rewrite the model and populate the link $scope.model.target.url = $scope.model.target.url.substring(0, indexOfAnchor); } - } - } else if (dialogOptions.anchors) { - $scope.anchorValues = dialogOptions.anchors; - } + } + } else if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } - function nodeSelectHandler(ev, args) { - if (args && args.event) { - args.event.preventDefault(); - args.event.stopPropagation(); - } + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } - eventsService.emit("dialogs.linkPicker.select", args); + eventsService.emit("dialogs.linkPicker.select", args); - if ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.model.target.id = args.node.id; - $scope.model.target.udi = args.node.udi; - $scope.model.target.name = args.node.name; + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.model.target.id = args.node.id; + $scope.model.target.udi = args.node.udi; + $scope.model.target.name = args.node.name; - if (args.node.id < 0) { - $scope.model.target.url = "/"; - } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - }); - } + if (args.node.id < 0) { + $scope.model.target.url = "/"; + } else { + contentResource.getById(args.node.id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + $scope.model.target.url = resp.urls[0]; + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + }); + } - if (!angular.isUndefined($scope.model.target.isMedia)) { - delete $scope.model.target.isMedia; - } - } + if (!angular.isUndefined($scope.model.target.isMedia)) { + delete $scope.model.target.isMedia; + } + } - function nodeExpandedHandler(ev, args) { - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } - $scope.switchToMediaPicker = function () { - userService.getCurrentUser().then(function (userData) { - $scope.mediaPickerOverlay = { - view: "mediapicker", - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, - show: true, - submit: function (model) { - var media = model.selectedImages[0]; + $scope.switchToMediaPicker = function () { + userService.getCurrentUser().then(function (userData) { + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; - $scope.model.target.id = media.id; - $scope.model.target.udi = media.udi; - $scope.model.target.isMedia = true; - $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + if (dialogOptions.ignoreUserStartNodes) { + startNodeId = -1; + startNodeIsVirtual = true; + } + $scope.mediaPickerOverlay = { + view: "mediapicker", + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + show: true, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + submit: function (model) { + var media = model.selectedImages[0]; - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; + $scope.model.target.id = media.id; + $scope.model.target.udi = media.udi; + $scope.model.target.isMedia = true; + $scope.model.target.name = media.name; + $scope.model.target.url = mediaHelper.resolveFile(media); - // make sure the content tree has nothing highlighted - $scope.dialogTreeEventHandler.syncTree({ - path: "-1", - tree: "content" - }); - } - }; - }); - }; + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; - $scope.hideSearch = function () { - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = null; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } + // make sure the content tree has nothing highlighted + $scope.dialogTreeEventHandler.syncTree({ + path: "-1", + tree: "content" + }); + } + }; + }); + }; - // method to select a search result - $scope.selectResult = function (evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { - event: evt, - node: result - }); - }; + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + }; - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - // Mini list view - $scope.selectListViewNode = function (node) { - node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { - node: node - }); - }; + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; + // Mini list view + $scope.selectListViewNode = function (node) { + node.selected = node.selected === true ? false : true; + nodeSelectHandler({}, { + node: node + }); + }; - function openMiniListView(node) { - $scope.miniListView = node; - } + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; - }); + function openMiniListView(node) { + $scope.miniListView = node; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index deba19aa11..c9f3ad54a7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -50,6 +50,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="{{section}}"> @@ -64,6 +65,7 @@ 0) { - entityResource.getAncestors(folder.id, "media") - .then(function(anc) { + entityResource.getAncestors(folder.id, "media", { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) + .then(function(anc) { $scope.path = _.filter(anc, function(f) { return f.path.indexOf($scope.startNodeId) !== -1; @@ -161,13 +168,13 @@ angular.module("umbraco") } else { $scope.path = []; } - + mediaTypeHelper.getAllowedImagetypes(folder.id) .then(function (types) { $scope.acceptedMediatypes = types; }); - - $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual; + + $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; $scope.currentFolder = folder; localStorageService.set("umbLastOpenedMediaNodeId", folder.id); @@ -263,6 +270,17 @@ angular.module("umbraco") } } + function hasFolderAccess(node) { + var nodePath = node.path ? node.path.split(',') : [node.id]; + + for (var i = 0; i < nodePath.length; i++) { + if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) + return true; + } + + return false; + } + function gotoStartNode(err) { $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } @@ -297,7 +315,8 @@ angular.module("umbraco") pageSize: 100, totalItems: 0, totalPages: 0, - filter: '' + filter: '', + ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }; getChildren($scope.currentFolder.id); } @@ -369,7 +388,7 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; - return mediaResource.getChildren(id) + return mediaResource.getChildren(id, { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) .then(function(data) { $scope.searchOptions.filter = ""; $scope.images = data.items ? data.items : []; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 615f86b384..e1ce332b48 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -1,517 +1,530 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", - function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) { - - var tree = null; - var dialogOptions = $scope.model; - $scope.treeReady = false; - $scope.dialogTreeEventHandler = $({}); - $scope.section = dialogOptions.section; - $scope.treeAlias = dialogOptions.treeAlias; - $scope.multiPicker = dialogOptions.multiPicker; - $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true; - // if you need to load a not initialized tree set this value to false - default is true - $scope.onlyInitialized = dialogOptions.onlyInitialized; - $scope.searchInfo = { - searchFromId: dialogOptions.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - $scope.model.selection = []; - - //Used for toggling an empty-state message - //Some trees can have no items (dictionary & forms email templates) - $scope.hasItems = true; - $scope.emptyStateMessage = dialogOptions.emptyStateMessage; - var node = dialogOptions.currentNode; - - //This is called from ng-init - //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. - //this is probably an anti pattern IMO and shouldn't be used - $scope.init = function (contentType) { - - if (contentType === "content") { - $scope.entityType = "Document"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectContent"); - } - } else if (contentType === "member") { - $scope.entityType = "Member"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMember"); - } - } else if (contentType === "media") { - $scope.entityType = "Media"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); - } - } - } - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; - - - //min / max values - if (dialogOptions.minNumber) { - dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); - } - if (dialogOptions.maxNumber) { - dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); - } - - if (dialogOptions.section === "member") { - $scope.entityType = "Member"; - } - else if (dialogOptions.section === "media") { - $scope.entityType = "Media"; - } - - // Search and listviews is only working for content, media and member section - var searchableSections = ["content", "media", "member"]; - - $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1; - - //if a alternative startnode is used, we need to check if it is a container - if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") { - entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) { - if (node.metaData.IsContainer) { - openMiniListView(node); - } - initTree(); - }); - } - else { - initTree(); - } - - //Configures filtering - if (dialogOptions.filter) { - - dialogOptions.filterExclude = false; - dialogOptions.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else if (angular.isObject(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else { - if (dialogOptions.filter.startsWith("!")) { - dialogOptions.filterExclude = true; - dialogOptions.filterTypes = dialogOptions.filter.substring(1); - } else { - dialogOptions.filterExclude = false; - dialogOptions.filterTypes = dialogOptions.filter; - } - - //used advanced filtering - if (dialogOptions.filter.startsWith("{")) { - dialogOptions.filterAdvanced = true; - //convert to object - dialogOptions.filter = angular.fromJson(dialogOptions.filter); - } - } - - $scope.filter = { - filterAdvanced: dialogOptions.filterAdvanced, - filterExclude: dialogOptions.filterExclude, - filter: dialogOptions.filterTypes - }; - } - - function initTree() { - //create the custom query string param for this tree - $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; - $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; - $scope.treeReady = true; - } - - function nodeExpandedHandler(ev, args) { - - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(ev, args) { - //args.tree contains children (args.tree.root.children) - $scope.hasItems = args.tree.root.children.length > 0; - - tree = args.tree; - - var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined"; - var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1"; - if (startNodeNotDefined && nodeHasPath) { - $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false }); - } - } - - //wires up selection - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, function (child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - if ($scope.model.select) { - $scope.model.select(args.node) - } else { - select(args.node.name, args.node.id); - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - - var rootNode = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - - if ($scope.multiPicker) { - if (entity) { - multiSelectItem(entity); - } else { - multiSelectItem(rootNode); - } - } - else { - $scope.model.selection.push(rootNode); - $scope.model.submit($scope.model); - } - } - else { - - if ($scope.multiPicker) { - - if (entity) { - multiSelectItem(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, $scope.entityType).then(function (ent) { - multiSelectItem(ent); - }); - } - - } - - else { - - $scope.hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.model.selection.push(entity); - $scope.model.submit($scope.model); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, $scope.entityType).then(function (ent) { - $scope.model.selection.push(ent); - $scope.model.submit($scope.model); - }); - } - } - } - } - - function multiSelectItem(item) { - - var found = false; - var foundIndex = 0; - - if ($scope.model.selection.length > 0) { - for (i = 0; $scope.model.selection.length > i; i++) { - var selectedItem = $scope.model.selection[i]; - if (selectedItem.id === item.id) { - found = true; - foundIndex = i; - } - } - } - - if (found) { - $scope.model.selection.splice(foundIndex, 1); - } else { - $scope.model.selection.push(item); - } - - } - - function performFiltering(nodes) { - - if (!dialogOptions.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, function (n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if (dialogOptions.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction(dialogOptions.filter) - ? _.filter(nodes, dialogOptions.filter) - : _.where(nodes, dialogOptions.filter); - - angular.forEach(filtered, function (value, key) { - value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - }); - } else { - var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; - - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } - - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, $scope.entityType).then(function (ents) { - $scope.submit(ents); - }); - }; - - /** method to select a search result */ - $scope.selectResult = function (evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - $scope.searchInfo.selectedSearchResults.push(result); - } - else { - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - - }; - - $scope.hideSearch = function () { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, function (c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, function (c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function (item) { - var childExists = _.find(child.children, function (c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - checkChildren(tree.root.children); - } - - - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = dialogOptions.startNodeId; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - $scope.onSearchResults = function (results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function (item) { - return !item.filtered; - }); - - $scope.searchInfo.results = results; - - //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.model.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - - $scope.selectListViewNode = function (node) { - select(node.name, node.id); - //toggle checked state - node.selected = node.selected === true ? false : true; - }; - - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; - - function openMiniListView(node) { - $scope.miniListView = node; - } - - }); +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", + function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) { + + var tree = null; + var dialogOptions = $scope.model; + $scope.treeReady = false; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true; + // if you need to load a not initialized tree set this value to false - default is true + $scope.onlyInitialized = dialogOptions.onlyInitialized; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + results: [], + selectedSearchResults: [] + } + + $scope.model.selection = []; + + //Used for toggling an empty-state message + //Some trees can have no items (dictionary & forms email templates) + $scope.hasItems = true; + $scope.emptyStateMessage = dialogOptions.emptyStateMessage; + var node = dialogOptions.currentNode; + + //This is called from ng-init + //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. + //this is probably an anti pattern IMO and shouldn't be used + $scope.init = function (contentType) { + + if (contentType === "content") { + $scope.entityType = "Document"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectContent"); + } + } else if (contentType === "member") { + $scope.entityType = "Member"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMember"); + } + } else if (contentType === "media") { + $scope.entityType = "Media"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); + } + } + } + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; + + + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + + if (dialogOptions.section === "member") { + $scope.entityType = "Member"; + } + else if (dialogOptions.section === "media") { + $scope.entityType = "Media"; + } + + // Search and listviews is only working for content, media and member section + var searchableSections = ["content", "media", "member"]; + + $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1; + + //if a alternative startnode is used, we need to check if it is a container + if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") { + entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) { + if (node.metaData.IsContainer) { + openMiniListView(node); + } + initTree(); + }); + } + else { + initTree(); + } + + //Configures filtering + if (dialogOptions.filter) { + + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else { + if (dialogOptions.filter.startsWith("!")) { + dialogOptions.filterExclude = true; + dialogOptions.filterTypes = dialogOptions.filter.substring(1); + } else { + dialogOptions.filterExclude = false; + dialogOptions.filterTypes = dialogOptions.filter; + } + + //used advanced filtering + if (dialogOptions.filter.startsWith("{")) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + + $scope.filter = { + filterAdvanced: dialogOptions.filterAdvanced, + filterExclude: dialogOptions.filterExclude, + filter: dialogOptions.filterTypes + }; + } + + function initTree() { + //create the custom query string param for this tree + var params = []; + + if (dialogOptions.startNodeId) + params.push("startNodeId=" + dialogOptions.startNodeId); + + if (dialogOptions.ignoreUserStartNodes) + params.push("ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes); + + if (dialogOptions.customTreeParams) + params.push(dialogOptions.customTreeParams); + + $scope.customTreeParams = params.join('&'); + + $scope.treeReady = true; + } + + function nodeExpandedHandler(ev, args) { + + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + + //check filter + performFiltering(args.children); + } + } + + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + //args.tree contains children (args.tree.root.children) + $scope.hasItems = args.tree.root.children.length > 0; + + tree = args.tree; + + var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined"; + var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1"; + if (startNodeNotDefined && nodeHasPath) { + $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false }); + } + + } + + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + + //unselect + select(args.node.name, args.node.id); + + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function (child) { + return child.id == args.node.id; + }); + + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } + else { + eventsService.emit("dialogs.treePickerController.select", args); + + if (args.node.filtered) { + return; + } + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + if ($scope.model.select) { + $scope.model.select(args.node) + } else { + select(args.node.name, args.node.id); + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + + } + } + + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + + var rootNode = { + alias: null, + icon: "icon-folder", + id: id, + name: text + }; + + if ($scope.multiPicker) { + if (entity) { + multiSelectItem(entity); + } else { + multiSelectItem(rootNode); + } + } + else { + $scope.model.selection.push(rootNode); + $scope.model.submit($scope.model); + } + } + else { + + if ($scope.multiPicker) { + + if (entity) { + multiSelectItem(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, $scope.entityType).then(function (ent) { + multiSelectItem(ent); + }); + } + + } + + else { + + $scope.hideSearch(); + + //if an entity has been passed in, use it + if (entity) { + $scope.model.selection.push(entity); + $scope.model.submit($scope.model); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, $scope.entityType).then(function (ent) { + $scope.model.selection.push(ent); + $scope.model.submit($scope.model); + }); + } + } + } + } + + function multiSelectItem(item) { + + var found = false; + var foundIndex = 0; + + if ($scope.model.selection.length > 0) { + for (i = 0; $scope.model.selection.length > i; i++) { + var selectedItem = $scope.model.selection[i]; + if (selectedItem.id === item.id) { + found = true; + foundIndex = i; + } + } + } + + if (found) { + $scope.model.selection.splice(foundIndex, 1); + } else { + $scope.model.selection.push(item); + } + + } + + function performFiltering(nodes) { + + if (!dialogOptions.filter) { + return; + } + + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function (n) { + return !angular.isObject(n.metaData.listViewNode); + }); + + if (dialogOptions.filterAdvanced) { + + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) + ? _.filter(nodes, dialogOptions.filter) + : _.where(nodes, dialogOptions.filter); + + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, function (value, key) { + + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, $scope.entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + + if (result.filtered) { + return; + } + + result.selected = result.selected === true ? false : true; + + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } + else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == result.id; + }); + } + + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + + }; + + $scope.hideSearch = function () { + + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function (c) { + return c.id == child.id; + }); + } + + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + + child.cssClasses = _.reject(child.cssClasses, function (c) { + return c === 'tree-node-slide-up-hide-active'; + }); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function (c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + + + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + $scope.onSearchResults = function (results) { + + //filter all items - this will mark an item as filtered + performFiltering(results); + + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function (item) { + return !item.filtered; + }); + + $scope.searchInfo.results = results; + + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.model.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + + $scope.selectListViewNode = function (node) { + select(node.name, node.id); + //toggle checked state + node.selected = node.selected === true ? false : true; + }; + + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + + function openMiniListView(node) { + $scope.miniListView = node; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index a3a5fd6107..c338e3402c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -10,6 +10,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="{{section}}"> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ee8c193b4b..ce836a8d68 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -66,6 +66,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper showOpenButton: false, showEditButton: false, showPathOnHover: false, + ignoreUserStartNodes: false, maxNumber: 1, minNumber : 0, startNode: { @@ -99,7 +100,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); - + $scope.model.config.ignoreUserStartNodes = ($scope.model.config.ignoreUserStartNodes === "1" ? true : false); + var entityType = $scope.model.config.startNode.type === "member" ? "Member" : $scope.model.config.startNode.type === "media" diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 21f9534848..a093ccb034 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,12 +1,20 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, $rootScope, $timeout, userService) { + + var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); + if (ignoreUserStartNodes === true) { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } } $scope.setImage = function(){ @@ -14,6 +22,7 @@ angular.module("umbraco") $scope.mediaPickerOverlay.view = "mediapicker"; $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; + $scope.mediaPickerOverlay.ignoreUserStartNodes = ignoreUserStartNodes; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 397438d5a0..cf81300b32 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -16,6 +16,7 @@ view: "linkpicker", currentTarget: currentTarget, anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)), + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -26,11 +27,23 @@ } function openMediaPicker(editor, currentTarget, userData) { + var ignoreUserStartNodes = false; + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + ignoreUserStartNodes = true; + startNodeId = -1; + startNodeIsVirtual = true; + } + vm.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, - showDetails: true, - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], + showDetails: true, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 7b8445a4dc..1f1305b0f0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -7,12 +7,19 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; + var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - userService.getCurrentUser().then(function(userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); + if (ignoreUserStartNodes === true) { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } } function setupViewModel() { @@ -105,6 +112,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl title: "Select media", startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index e4f2ae303f..c7e67a0a42 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -67,10 +67,11 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en url: link.url, target: link.target } : null; - + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: target, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function (model) { if (model.target.url || model.target.anchor) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 142298e0d8..6047169c54 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -25,6 +25,7 @@ $scope.contentPickerOverlay.view = "contentpicker"; $scope.contentPickerOverlay.multiPicker = false; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true: false; $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; $scope.contentPickerOverlay.submit = function(model) { @@ -50,6 +51,7 @@ $scope.contentPickerOverlay.view = "contentpicker"; $scope.contentPickerOverlay.multiPicker = false; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; $scope.contentPickerOverlay.submit = function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 1fc648083e..f8dea2ee8b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -274,6 +274,7 @@ angular.module("umbraco") view: "linkpicker", currentTarget: currentTarget, anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -285,14 +286,24 @@ angular.module("umbraco") //Create the insert media plugin tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ + var ignoreUserStartNodes = false; + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + ignoreUserStartNodes = true; + startNodeId = -1; + startNodeIsVirtual = true; + } $scope.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, showDetails: true, disableFolderSelect: true, - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index b9d63df9f3..9d7ff0cf40 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -40,4 +40,5 @@ Pixels + \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index beff2c2615..b8d9c8bcb6 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -262,10 +262,11 @@ namespace Umbraco.Web.Editors /// Gets the content json for the content id ///
    /// + /// If set to true, user and group start node permissions will be ignored. /// [OutgoingEditorModelEvent] [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id) + public ContentItemDisplay GetById(int id, [FromUri]bool ignoreUserStartNodes = false) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) @@ -1116,6 +1117,7 @@ namespace Umbraco.Web.Editors /// The content to lookup, if the contentItem is not specified /// /// Specifies the already resolved content item to check against + /// If set to true, user and group start node permissions will be ignored. /// internal static bool CheckPermissions( IDictionary storage, @@ -1125,7 +1127,8 @@ namespace Umbraco.Web.Editors IEntityService entityService, int nodeId, char[] permissionsToCheck = null, - IContent contentItem = null) + IContent contentItem = null, + bool ignoreUserStartNodes = false) { if (storage == null) throw new ArgumentNullException("storage"); if (user == null) throw new ArgumentNullException("user"); @@ -1146,6 +1149,11 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + if(ignoreUserStartNodes) + { + return true; + } + var hasPathAccess = (nodeId == Constants.System.Root) ? user.HasContentRootAccess(entityService) : (nodeId == Constants.System.RecycleBinContent) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 8bde435ef6..a3f76db4f2 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -83,8 +83,25 @@ namespace Umbraco.Web.Editors /// A starting point for the search, generally a node id, but for members this is a member type alias /// /// + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [HttpGet] public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) + { + return Search(query, type, false, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// If set to true, user and group start node permissions will be ignored. + /// + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, bool? ignoreUserStartNodes, string searchFrom = null) { //TODO: Should we restrict search results based on what app the user has access to? // - Theoretically you shouldn't be able to see member data if you don't have access to members right? @@ -92,7 +109,7 @@ namespace Umbraco.Web.Editors if (string.IsNullOrEmpty(query)) return Enumerable.Empty(); - return ExamineSearch(query, type, searchFrom); + return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value); } /// @@ -527,6 +544,7 @@ namespace Umbraco.Web.Editors } } + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, @@ -535,6 +553,20 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, type, pageNumber, pageSize, + false, orderBy, orderDirection, filter); + } + + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + bool ignoreUserStartNodes, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -562,7 +594,7 @@ namespace Umbraco.Web.Editors break; } - entities = aids == null || aids.Contains(Constants.System.Root) + entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); } @@ -598,9 +630,15 @@ namespace Umbraco.Web.Editors } } + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) { - return GetResultForAncestors(id, type); + return GetResultForAncestors(id, type, false); + } + + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, bool ignoreUserStartNodes) + { + return GetResultForAncestors(id, type, ignoreUserStartNodes); } public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) @@ -614,11 +652,12 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null) + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) { long total; - return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom); + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); } @@ -645,7 +684,7 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType) + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, bool ignoreUserStartNodes = false) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) @@ -654,35 +693,38 @@ namespace Umbraco.Web.Editors var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - int[] aids = null; - switch (entityType) + if (ignoreUserStartNodes == false) { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - if (aids != null) - { - var lids = new List(); - var ok = false; - foreach (var i in ids) + int[] aids = null; + switch (entityType) { - if (ok) - { - lids.Add(i); - continue; - } - if (aids.Contains(i)) - { - lids.Add(i); - ok = true; - } + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) + { + if (ok) + { + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; + } + } + ids = lids.ToArray(); } - ids = lids.ToArray(); } return ids.Length == 0 diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index d411bf2197..6c0c293572 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -264,11 +264,12 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "") + string filter = "", + bool ignoreUserStartNodes = false) { //if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes - if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) + if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false)) { if (pageNumber > 0) return new PagedResult>(0, 0, 0); @@ -346,6 +347,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Guid id, @@ -360,7 +362,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(id); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); } throw new HttpResponseException(HttpStatusCode.NotFound); } @@ -400,6 +402,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, @@ -417,7 +420,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); } } @@ -433,7 +436,8 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "") + string filter = "", + bool ignoreUserStartNodes = false) { foreach (var type in new[] { typeof(int), typeof(Guid) }) { diff --git a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs index 5b99264113..0039385e5f 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs @@ -15,10 +15,11 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"startNodeId", "-1"}, + {"startNodeId", "-1"}, {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -39,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors { public ContentPickerPreValueEditor() { - //create the fields + //create the fields Fields.Add(new PreValueField() { Key = "showOpenButton", @@ -48,6 +49,13 @@ namespace Umbraco.Web.PropertyEditors Description = "Opens the node in a dialog" }); Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNodeId", View = "treepicker", @@ -60,4 +68,4 @@ namespace Umbraco.Web.PropertyEditors } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index af30b4ceeb..4a8803a099 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -136,6 +136,9 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public string Rte { get; set; } + + [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } } #region Application event handler, used to bind to events on startup diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs index 2d9ee68b3b..6aa7ab4a54 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs @@ -57,6 +57,13 @@ namespace Umbraco.Web.PropertyEditors Description = "Do not allow folders to be picked." }); Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNodeId", View = "mediapicker", @@ -69,4 +76,4 @@ namespace Umbraco.Web.PropertyEditors } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs index f43ecd48be..f57a9951b6 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.PropertyEditors {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -38,6 +39,13 @@ namespace Umbraco.Web.PropertyEditors { //create the fields Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNode", View = "treesource", @@ -118,4 +126,4 @@ namespace Umbraco.Web.PropertyEditors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index a058a16b8a..f3ae317efa 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -32,6 +32,13 @@ namespace Umbraco.Web.PropertyEditors { public MultiUrlPickerPreValueEditor() { + Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); Fields.Add(new PreValueField { Key = "minNumber", diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs index 541dccaa4e..a96c0724ff 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -32,8 +33,11 @@ namespace Umbraco.Web.PropertyEditors internal class RelatedLinksPreValueEditor : PreValueEditor { + [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } + [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int Maximum { get; set; } + public int Maximum { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs index cd08a8ad45..69445bc304 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs @@ -23,6 +23,14 @@ namespace Umbraco.Web.PropertyEditors Key = "editor" }); + Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Name = "Hide Label", diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 38347de9ed..8845e9c323 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.Search internal class UmbracoTreeSearcher { /// + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Searches for results based on the entity type /// /// @@ -28,12 +29,37 @@ namespace Umbraco.Web.Search /// /// /// + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public IEnumerable ExamineSearch( UmbracoHelper umbracoHelper, string query, UmbracoEntityTypes entityType, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + return ExamineSearch(umbracoHelper, query, entityType, pageSize, pageIndex, out totalFound, false, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// + /// + /// If set to true, user and group start node permissions will be ignored. + /// + public IEnumerable ExamineSearch( + UmbracoHelper umbracoHelper, + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, out long totalFound, bool ignoreUserStartNodes, string searchFrom = null) { var sb = new StringBuilder(); @@ -61,12 +87,12 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: type = "media"; var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(appContext.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, appContext.Services.EntityService); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, appContext.Services.EntityService); break; case UmbracoEntityTypes.Document: type = "content"; var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(appContext.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, appContext.Services.EntityService); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, appContext.Services.EntityService); break; default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); @@ -203,7 +229,7 @@ namespace Umbraco.Web.Search } } - private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, IEntityService entityService) + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, bool ignoreUserStartNodes, IEntityService entityService) { if (sb == null) throw new ArgumentNullException("sb"); if (entityService == null) throw new ArgumentNullException("entityService"); @@ -228,7 +254,7 @@ namespace Umbraco.Web.Search // make sure we don't find anything sb.Append("+__Path:none "); } - else if (startNodeIds.Contains(-1) == false) // -1 = no restriction + else if (startNodeIds.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction { var entityPaths = entityService.GetAllPaths(objectType, startNodeIds); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index af38e21546..2624c89b56 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); - if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false) + if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false) { node.AdditionalData["noAccess"] = true; } @@ -91,7 +91,7 @@ namespace Umbraco.Web.Trees { bool hasPathAccess; var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess); - if (entityIsAncestorOfStartNodes == false) + if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false) return null; var treeNode = GetSingleTreeNode(e, parentId, queryStrings); @@ -101,7 +101,7 @@ namespace Umbraco.Web.Trees //the node so we need to return null; return null; } - if (hasPathAccess == false) + if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; } @@ -141,7 +141,7 @@ namespace Umbraco.Web.Trees // ensure that the user has access to that node, otherwise return the empty tree nodes collection // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access - if (HasPathAccess(id, queryStrings) == false) + if (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false) { LogHelper.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id); return nodes; @@ -158,7 +158,7 @@ namespace Umbraco.Web.Trees // get child entities - if id is root, but user's start nodes do not contain the // root node, this returns the start nodes instead of root's children - var entities = GetChildEntities(id).ToList(); + var entities = GetChildEntities(id, queryStrings).ToList(); nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); // if the user does not have access to the root node, what we have is the start nodes, @@ -190,7 +190,7 @@ namespace Umbraco.Web.Trees protected abstract UmbracoObjectTypes UmbracoObjectType { get; } - protected IEnumerable GetChildEntities(string id) + protected IEnumerable GetChildEntities(string id, FormDataCollection queryStrings) { // try to parse id as an integer else use GetEntityFromId // which will grok Guids, Udis, etc and let use obtain the id diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 98eb2ec8bc..2ea41ff128 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -349,6 +349,16 @@ namespace Umbraco.Web.Trees return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); } + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); + } + /// /// An event that allows developers to modify the tree node collection that is being rendered /// diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index 7f6fa28187..c79b9f8781 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -8,7 +8,8 @@ public const string IsDialog = "isDialog"; public const string Application = "application"; public const string StartNodeId = "startNodeId"; + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; //public const string OnNodeClick = "OnNodeClick"; //public const string RenderParent = "RenderParent"; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 18880b9f96..64fe9a4b65 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Globalization; @@ -52,7 +52,7 @@ namespace Umbraco.Web.WebApi.Filters _paramName = paramName; _permissionToCheck = ActionBrowse.Instance.Letter; - } + } public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) : this(paramName) @@ -72,6 +72,9 @@ namespace Umbraco.Web.WebApi.Filters //not logged in throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); } + + var ignoreUserStartNodes = actionContext.ActionArguments.ContainsKey("ignoreUserStartNodes") && + bool.Parse(actionContext.ActionArguments.GetValueAsString("ignoreUserStartNodes")); int nodeId; if (_nodeId.HasValue == false) @@ -124,9 +127,11 @@ namespace Umbraco.Web.WebApi.Filters actionContext.Request.Properties, UmbracoContext.Current.Security.CurrentUser, ApplicationContext.Current.Services.UserService, - ApplicationContext.Current.Services.ContentService, - ApplicationContext.Current.Services.EntityService, - nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null)) + ApplicationContext.Current.Services.ContentService, + ApplicationContext.Current.Services.EntityService, + nodeId, + _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, + ignoreUserStartNodes: ignoreUserStartNodes)) { base.OnActionExecuting(actionContext); } diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index beb67f3395..23d4fb871c 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -3,11 +3,13 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Web; using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; namespace Umbraco.Web.WebApi.Filters { @@ -77,7 +79,12 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - FilterBasedOnStartNode(items, user); + bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes); + + if (ignoreUserStartNodes == false) + { + FilterBasedOnStartNode(items, user); + } } internal void FilterBasedOnStartNode(IList items, IUser user) From f829bb8023c84c61b6c472e4e7afb39a12778720 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 19 Jun 2019 18:40:42 +0200 Subject: [PATCH 064/218] Fix "comma-dangle" gulp build error --- .../src/common/services/editorstate.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js index d00edae410..97a9ac5c4b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js @@ -63,7 +63,7 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope) */ getCurrent: function () { return current; - }, + } }; From fcfb10765321b2297f3f61fe4dad511c602765b9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jun 2019 15:40:57 +1000 Subject: [PATCH 065/218] Fixes up UmbracoContextFactory to ensure it's not returning a disposed Umbraco context --- src/Umbraco.Web/UmbracoContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoContextFactory.cs b/src/Umbraco.Web/UmbracoContextFactory.cs index 2a812036bf..1efdade458 100644 --- a/src/Umbraco.Web/UmbracoContextFactory.cs +++ b/src/Umbraco.Web/UmbracoContextFactory.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web public UmbracoContextReference EnsureUmbracoContext(HttpContextBase httpContext = null) { var currentUmbracoContext = _umbracoContextAccessor.UmbracoContext; - if (currentUmbracoContext != null) + if (currentUmbracoContext != null && !currentUmbracoContext.Disposed) return new UmbracoContextReference(currentUmbracoContext, false, _umbracoContextAccessor); From f6ac64f705efd4cd8271a6b73f0ff7c8a64ffb65 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jun 2019 15:53:25 +1000 Subject: [PATCH 066/218] makes TreeChangeExtensions public --- src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs index b3d15bd612..a5f5efdba9 100644 --- a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs +++ b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs @@ -2,9 +2,9 @@ namespace Umbraco.Core.Services.Changes { - internal static class TreeChangeExtensions + public static class TreeChangeExtensions { - public static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes) + internal static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes) { return new TreeChange.EventArgs(changes); } From 5ea6ebcc6683010d4d711cf9aa25c1b54d903f1b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jun 2019 17:58:40 +1000 Subject: [PATCH 067/218] Revert "Fixes up UmbracoContextFactory to ensure it's not returning a disposed Umbraco context" This reverts commit fcfb10765321b2297f3f61fe4dad511c602765b9. --- src/Umbraco.Web/UmbracoContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoContextFactory.cs b/src/Umbraco.Web/UmbracoContextFactory.cs index 1efdade458..2a812036bf 100644 --- a/src/Umbraco.Web/UmbracoContextFactory.cs +++ b/src/Umbraco.Web/UmbracoContextFactory.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web public UmbracoContextReference EnsureUmbracoContext(HttpContextBase httpContext = null) { var currentUmbracoContext = _umbracoContextAccessor.UmbracoContext; - if (currentUmbracoContext != null && !currentUmbracoContext.Disposed) + if (currentUmbracoContext != null) return new UmbracoContextReference(currentUmbracoContext, false, _umbracoContextAccessor); From f4b2ff93fc02a30066cc26244dac0aec4711af96 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jun 2019 19:10:32 +1000 Subject: [PATCH 068/218] Fixes how Umbraco Context is injected into DI so that LightInject doesn't try to control it's lifetime. --- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 5a464701e0..a37f9c3588 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -92,9 +92,9 @@ namespace Umbraco.Web.Runtime // we should stop injecting UmbracoContext and always inject IUmbracoContextAccessor, however at the moment // there are tons of places (controllers...) which require UmbracoContext in their ctor - so let's register - // a way to inject the UmbracoContext - and register it per-request to be more efficient - // TODO: stop doing this - composition.Register(factory => factory.GetInstance().UmbracoContext, Lifetime.Request); + // a way to inject the UmbracoContext - DO NOT register this as Lifetime.Request since LI will dispose the context + // in it's own way but we don't want that to happen, we manage its lifetime ourselves. + composition.Register(factory => factory.GetInstance().UmbracoContext); composition.Register(factory => { From 4e4487167126d89dc2174592d529dc73b5f9828b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 20 Jun 2019 15:57:31 +0200 Subject: [PATCH 069/218] The NuGet packages needs an extra DotNetCompilerPlatform transform to be sure it gets applied as it sometimes didn't Added SignalR binding redirect for Umbraco Cloud Added System.Web.Http.WebHost binding redirect for uCommerce --- build/NuSpecs/tools/Web.config.install.xdt | 18 ++++++++++++++++-- src/Umbraco.Web.UI/web.Template.config | 8 ++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index a381a58c3e..4f8a1927a8 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -324,7 +324,6 @@ - @@ -337,6 +336,9 @@ + + + @@ -369,7 +371,7 @@ - + @@ -398,6 +400,18 @@ + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index fe25dd0c0f..583952f29d 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -466,6 +466,14 @@ + + + + + + + + From 4df838c20c5f128d429602f4f933f851469dfe0c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 20 Jun 2019 16:02:42 +0200 Subject: [PATCH 070/218] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1358 - Changed the ignoreUserStartNodes in public endpoints to the data type id, and then look up the setting --- src/Umbraco.Core/Services/DataTypeService.cs | 1296 ++++----- src/Umbraco.Core/Services/IDataTypeService.cs | 335 +-- .../tree/umbtreesearchbox.directive.js | 10 +- .../src/common/resources/content.resource.js | 122 +- .../src/common/resources/entity.resource.js | 1168 ++++---- .../src/common/resources/media.resource.js | 1118 ++++---- .../src/common/services/search.service.js | 338 +-- .../common/dialogs/linkpicker.controller.js | 334 +-- .../src/views/common/dialogs/linkpicker.html | 173 +- .../common/dialogs/treepicker.controller.js | 861 +++--- .../overlays/contentpicker/contentpicker.html | 2 +- .../linkpicker/linkpicker.controller.js | 12 +- .../overlays/linkpicker/linkpicker.html | 3 +- .../mediaPicker/mediapicker.controller.js | 852 +++--- .../treepicker/treepicker.controller.js | 6 +- .../overlays/treepicker/treepicker.html | 112 +- .../contentpicker/contentpicker.controller.js | 732 ++--- .../grid/editors/media.controller.js | 6 +- .../grid/editors/rte.controller.js | 12 +- .../mediapicker/mediapicker.controller.js | 369 +-- .../multiurlpicker.controller.js | 5 +- .../relatedlinks/relatedlinks.controller.js | 484 ++-- .../propertyeditors/rte/rte.controller.js | 805 +++--- src/Umbraco.Web/Editors/ContentController.cs | 2454 ++++++++--------- src/Umbraco.Web/Editors/EntityController.cs | 2003 +++++++------- src/Umbraco.Web/Editors/MediaController.cs | 2031 +++++++------- .../ContentEditing/ContentPropertyBasic.cs | 97 +- .../Mapping/ContentPropertyBasicConverter.cs | 153 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 808 +++--- .../Trees/TreeQueryStringParameters.cs | 30 +- ...EnsureUserPermissionForContentAttribute.cs | 302 +- .../FilterAllowedOutgoingMediaAttribute.cs | 11 +- 32 files changed, 8547 insertions(+), 8497 deletions(-) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 0ee3e9b823..43c2b69c47 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -1,640 +1,656 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.PropertyEditors; -using umbraco.interfaces; -using Umbraco.Core.Exceptions; - -namespace Umbraco.Core.Services -{ - /// - /// Represents the DataType Service, which is an easy access to operations involving - /// - public class DataTypeService : ScopeRepositoryService, IDataTypeService - { - - public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - } - - #region Containers - - public Attempt> CreateContainer(int parentId, string name, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - - try - { - var container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) - { - Name = name, - ParentId = parentId, - CreatorId = userId - }; - - if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - repo.AddOrUpdate(container); - uow.Commit(); - - uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); - //TODO: Audit trail ? - - return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); - } - catch (Exception ex) - { - return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); - } - } - } - - public EntityContainer GetContainer(int containerId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(containerId); - } - } - - public EntityContainer GetContainer(Guid containerId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(containerId); - } - } - - public IEnumerable GetContainers(string name, int level) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(name, level); - } - } - - public IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition) - { - var ancestorIds = dataTypeDefinition.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => - { - var asInt = x.TryConvertTo(); - if (asInt) return asInt.Result; - return int.MinValue; - }) - .Where(x => x != int.MinValue && x != dataTypeDefinition.Id) - .ToArray(); - - return GetContainers(ancestorIds); - } - - public IEnumerable GetContainers(int[] containerIds) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.GetAll(containerIds); - } - } - - public Attempt SaveContainer(EntityContainer container, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) - { - var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); - return OperationStatus.Exception(evtMsgs, ex); - } - - if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - { - var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); - return OperationStatus.Exception(evtMsgs, ex); - } - - using (var uow = UowProvider.GetUnitOfWork()) - { - if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) - return OperationStatus.Cancelled(evtMsgs); - - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - repo.AddOrUpdate(container); - uow.Commit(); - uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); - } - - //TODO: Audit trail ? - - return OperationStatus.Success(evtMsgs); - } - - public Attempt DeleteContainer(int containerId, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - var container = repo.Get(containerId); - if (container == null) return OperationStatus.NoOperation(evtMsgs); - - if (uow.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs(container, evtMsgs))) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - repo.Delete(container); - uow.Commit(); - - uow.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs(container, evtMsgs)); - - return OperationStatus.Success(evtMsgs); - //TODO: Audit trail ? - } - } - - #endregion - - /// - /// Gets a by its Name - /// - /// Name of the - /// - public IDataTypeDefinition GetDataTypeDefinitionByName(string name) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetByQuery(new Query().Where(x => x.Name == name)).FirstOrDefault(); - } - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// - public IDataTypeDefinition GetDataTypeDefinitionById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - public IDataTypeDefinition GetDataTypeDefinitionById(Guid id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - var query = Query.Builder.Where(x => x.Key == id); - - var definitions = repository.GetByQuery(query); - return definitions.FirstOrDefault(); - } - } - - /// - /// Gets a by its control Id - /// - /// Id of the DataType control - /// Collection of objects with a matching contorl id - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - public IEnumerable GetDataTypeDefinitionByControlId(Guid id) - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); - return GetDataTypeDefinitionByPropertyEditorAlias(alias); - } - - /// - /// Gets a by its control Id - /// - /// Alias of the property editor - /// Collection of objects with a matching contorl id - public IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - var query = Query.Builder.Where(x => x.PropertyEditorAlias == propertyEditorAlias); - return repository.GetByQuery(query); - } - } - - /// - /// Gets all objects or those with the ids passed in - /// - /// Optional array of Ids - /// An enumerable list of objects - public IEnumerable GetAllDataTypeDefinitions(params int[] ids) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Gets all prevalues for an - /// - /// Id of the to retrieve prevalues from - /// An enumerable list of string values - public IEnumerable GetPreValuesByDataTypeId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - //now convert the collection to a string list - var collection = repository.GetPreValuesCollectionByDataTypeId(id); - //now convert the collection to a string list - return collection.FormatAsDictionary().Select(x => x.Value.Value).ToList(); - } - } - - /// - /// Returns the PreValueCollection for the specified data type - /// - /// - /// - public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetPreValuesCollectionByDataTypeId(id); - } - } - - /// - /// Gets a specific PreValue by its Id - /// - /// Id of the PreValue to retrieve the value from - /// PreValue as a string - public string GetPreValueAsString(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetPreValueAsString(id); - } - } - - public Attempt> Move(IDataTypeDefinition toMove, int parentId) - { - var evtMsgs = EventMessagesFactory.Get(); - - var moveInfo = new List>(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); - var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); - if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs)) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - try - { - EntityContainer container = null; - if (parentId > 0) - { - container = containerRepository.Get(parentId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); - } - moveInfo.AddRange(repository.Move(toMove, container)); - } - catch (DataOperationException ex) - { - return Attempt.Fail( - new OperationStatus(ex.Operation, evtMsgs)); - } - uow.Commit(); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - uow.Events.Dispatch(Moved, this, moveEventArgs); - } - - return Attempt.Succeed( - new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); - } - - /// - /// Saves an - /// - /// to save - /// Id of the user issueing the save - public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - - if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name)) - { - throw new ArgumentException("Cannot save datatype with empty name."); - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - - Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); - uow.Commit(); - } - } - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - public void Save(IEnumerable dataTypeDefinitions, int userId = 0) - { - Save(dataTypeDefinitions, userId, true); - } - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - /// Boolean indicating whether or not to raise events - public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinitions); - if (raiseEvents) - { - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - foreach (var dataTypeDefinition in dataTypeDefinitions) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - } - - Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); - uow.Commit(); - } - } - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of string values to save - [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - public void SavePreValues(int dataTypeId, IEnumerable values) - { - //TODO: Should we raise an event here since we are really saving values for the data type? - - using (var uow = UowProvider.GetUnitOfWork()) - { - var sortOrderObj = - uow.Database.ExecuteScalar( - "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); - int sortOrder; - if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) - { - sortOrder = 1; - } - - foreach (var value in values) - { - var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; - uow.Database.Insert(dto); - sortOrder++; - } - - uow.Commit(); - } - } - - /// - /// Saves/updates the pre-values - /// - /// - /// - /// - /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors - /// like 'dropdown list publishing keys' - /// - public void SavePreValues(int dataTypeId, IDictionary values) - { - var dtd = GetDataTypeDefinitionById(dataTypeId); - if (dtd == null) - { - throw new InvalidOperationException("No data type found for id " + dataTypeId); - } - SavePreValues(dtd, values); - } - - /// - /// Saves/updates the pre-values - /// - /// - /// - /// - /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors - /// like 'dropdown list publishing keys' - /// - public void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) - { - //TODO: Should we raise an event here since we are really saving values for the data type? - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - repository.AddOrUpdatePreValues(dataTypeDefinition, values); - uow.Commit(); - } - } - - /// - /// This will save a data type and it's pre-values in one transaction - /// - /// - /// - /// - public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - - // if preValues contain the data type, override the data type definition accordingly - if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) - dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - dataTypeDefinition.CreatorId = userId; - - //add/update the dtd - repository.AddOrUpdate(dataTypeDefinition); - - //add/update the prevalues - repository.AddOrUpdatePreValues(dataTypeDefinition, values); - - Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - } - } - - /// - /// Deletes an - /// - /// - /// Please note that deleting a will remove - /// all the data that references this . - /// - /// to delete - /// Optional Id of the user issueing the deletion - public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - repository.Delete(dataTypeDefinition); - - Audit(uow, AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(Deleted, this, deleteEventArgs); - } - } - - /// - /// Gets the specified by it's unique ID - /// - /// Id of the DataType, which corresponds to the Guid Id of the control - /// object - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - public IDataType GetDataTypeById(Guid id) - { - return DataTypesResolver.Current.GetById(id); - } - - /// - /// Gets a complete list of all registered 's - /// - /// An enumerable list of objects - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - public IEnumerable GetAllDataTypes() - { - return DataTypesResolver.Current.DataTypes; - } - - private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) - { - var auditRepo = RepositoryFactory.CreateAuditRepository(uow); - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - } - - #region Event Handlers - - public static event TypedEventHandler> SavingContainer; - public static event TypedEventHandler> SavedContainer; - public static event TypedEventHandler> DeletingContainer; - public static event TypedEventHandler> DeletedContainer; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - #endregion - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.PropertyEditors; +using umbraco.interfaces; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Services +{ + /// + /// Represents the DataType Service, which is an easy access to operations involving + /// + public class DataTypeService : ScopeRepositoryService, IDataTypeService + { + + public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + } + + #region Containers + + public Attempt> CreateContainer(int parentId, string name, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + + try + { + var container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) + { + Name = name, + ParentId = parentId, + CreatorId = userId + }; + + if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + repo.AddOrUpdate(container); + uow.Commit(); + + uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); + //TODO: Audit trail ? + + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); + } + catch (Exception ex) + { + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); + } + } + } + + public EntityContainer GetContainer(int containerId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(containerId); + } + } + + public EntityContainer GetContainer(Guid containerId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(containerId); + } + } + + public IEnumerable GetContainers(string name, int level) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(name, level); + } + } + + public IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition) + { + var ancestorIds = dataTypeDefinition.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => + { + var asInt = x.TryConvertTo(); + if (asInt) return asInt.Result; + return int.MinValue; + }) + .Where(x => x != int.MinValue && x != dataTypeDefinition.Id) + .ToArray(); + + return GetContainers(ancestorIds); + } + + public IEnumerable GetContainers(int[] containerIds) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.GetAll(containerIds); + } + } + + public Attempt SaveContainer(EntityContainer container, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) + { + var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); + return OperationStatus.Exception(evtMsgs, ex); + } + + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationStatus.Exception(evtMsgs, ex); + } + + using (var uow = UowProvider.GetUnitOfWork()) + { + if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + return OperationStatus.Cancelled(evtMsgs); + + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + repo.AddOrUpdate(container); + uow.Commit(); + uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); + } + + //TODO: Audit trail ? + + return OperationStatus.Success(evtMsgs); + } + + public Attempt DeleteContainer(int containerId, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + var container = repo.Get(containerId); + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (uow.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs(container, evtMsgs))) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + repo.Delete(container); + uow.Commit(); + + uow.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs(container, evtMsgs)); + + return OperationStatus.Success(evtMsgs); + //TODO: Audit trail ? + } + } + + #endregion + + /// + /// Gets a by its Name + /// + /// Name of the + /// + public IDataTypeDefinition GetDataTypeDefinitionByName(string name) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetByQuery(new Query().Where(x => x.Name == name)).FirstOrDefault(); + } + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// + public IDataTypeDefinition GetDataTypeDefinitionById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its unique guid Id + /// + /// Unique guid Id of the DataType + /// + public IDataTypeDefinition GetDataTypeDefinitionById(Guid id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + var query = Query.Builder.Where(x => x.Key == id); + + var definitions = repository.GetByQuery(query); + return definitions.FirstOrDefault(); + } + } + + /// + /// Gets a by its control Id + /// + /// Id of the DataType control + /// Collection of objects with a matching contorl id + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + public IEnumerable GetDataTypeDefinitionByControlId(Guid id) + { + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); + return GetDataTypeDefinitionByPropertyEditorAlias(alias); + } + + /// + /// Gets a by its control Id + /// + /// Alias of the property editor + /// Collection of objects with a matching contorl id + public IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + var query = Query.Builder.Where(x => x.PropertyEditorAlias == propertyEditorAlias); + return repository.GetByQuery(query); + } + } + + /// + /// Gets all objects or those with the ids passed in + /// + /// Optional array of Ids + /// An enumerable list of objects + public IEnumerable GetAllDataTypeDefinitions(params int[] ids) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetAll(ids); + } + } + + /// + /// Gets all prevalues for an + /// + /// Id of the to retrieve prevalues from + /// An enumerable list of string values + public IEnumerable GetPreValuesByDataTypeId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + //now convert the collection to a string list + var collection = repository.GetPreValuesCollectionByDataTypeId(id); + //now convert the collection to a string list + return collection.FormatAsDictionary().Select(x => x.Value.Value).ToList(); + } + } + + /// + /// Returns the PreValueCollection for the specified data type + /// + /// + /// + public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetPreValuesCollectionByDataTypeId(id); + } + } + + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string + public string GetPreValueAsString(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetPreValueAsString(id); + } + } + + public Attempt> Move(IDataTypeDefinition toMove, int parentId) + { + var evtMsgs = EventMessagesFactory.Get(); + + var moveInfo = new List>(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); + var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); + if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs)) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + try + { + EntityContainer container = null; + if (parentId > 0) + { + container = containerRepository.Get(parentId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); + } + moveInfo.AddRange(repository.Move(toMove, container)); + } + catch (DataOperationException ex) + { + return Attempt.Fail( + new OperationStatus(ex.Operation, evtMsgs)); + } + uow.Commit(); + moveEventArgs.MoveInfoCollection = moveInfo; + moveEventArgs.CanCancel = false; + uow.Events.Dispatch(Moved, this, moveEventArgs); + } + + return Attempt.Succeed( + new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); + } + + public bool IsDataTypeIgnoringUserStartNodes(Guid key) + { + var dataType = GetDataTypeDefinitionById(key); + + if (dataType != null) + { + var preValues = GetPreValuesCollectionByDataTypeId(dataType.Id); + if (preValues.PreValuesAsDictionary.TryGetValue("ignoreUserStartNodes", out var preValue)) + { + return string.Equals(preValue.Value, "1", StringComparison.InvariantCulture); + } + } + + return false; + } + + /// + /// Saves an + /// + /// to save + /// Id of the user issueing the save + public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + + if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name)) + { + throw new ArgumentException("Cannot save datatype with empty name."); + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + + Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); + uow.Commit(); + } + } + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + public void Save(IEnumerable dataTypeDefinitions, int userId = 0) + { + Save(dataTypeDefinitions, userId, true); + } + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinitions); + if (raiseEvents) + { + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + foreach (var dataTypeDefinition in dataTypeDefinitions) + { + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + } + + Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); + uow.Commit(); + } + } + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of string values to save + [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] + public void SavePreValues(int dataTypeId, IEnumerable values) + { + //TODO: Should we raise an event here since we are really saving values for the data type? + + using (var uow = UowProvider.GetUnitOfWork()) + { + var sortOrderObj = + uow.Database.ExecuteScalar( + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); + int sortOrder; + if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) + { + sortOrder = 1; + } + + foreach (var value in values) + { + var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; + uow.Database.Insert(dto); + sortOrder++; + } + + uow.Commit(); + } + } + + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(int dataTypeId, IDictionary values) + { + var dtd = GetDataTypeDefinitionById(dataTypeId); + if (dtd == null) + { + throw new InvalidOperationException("No data type found for id " + dataTypeId); + } + SavePreValues(dtd, values); + } + + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) + { + //TODO: Should we raise an event here since we are really saving values for the data type? + + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + uow.Commit(); + } + } + + /// + /// This will save a data type and it's pre-values in one transaction + /// + /// + /// + /// + public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + + // if preValues contain the data type, override the data type definition accordingly + if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) + dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + dataTypeDefinition.CreatorId = userId; + + //add/update the dtd + repository.AddOrUpdate(dataTypeDefinition); + + //add/update the prevalues + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + + Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + } + } + + /// + /// Deletes an + /// + /// + /// Please note that deleting a will remove + /// all the data that references this . + /// + /// to delete + /// Optional Id of the user issueing the deletion + public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + repository.Delete(dataTypeDefinition); + + Audit(uow, AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); + uow.Commit(); + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(Deleted, this, deleteEventArgs); + } + } + + /// + /// Gets the specified by it's unique ID + /// + /// Id of the DataType, which corresponds to the Guid Id of the control + /// object + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + public IDataType GetDataTypeById(Guid id) + { + return DataTypesResolver.Current.GetById(id); + } + + /// + /// Gets a complete list of all registered 's + /// + /// An enumerable list of objects + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + public IEnumerable GetAllDataTypes() + { + return DataTypesResolver.Current.DataTypes; + } + + private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) + { + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + } + + #region Event Handlers + + public static event TypedEventHandler> SavingContainer; + public static event TypedEventHandler> SavedContainer; + public static event TypedEventHandler> DeletingContainer; + public static event TypedEventHandler> DeletedContainer; + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; + #endregion + } +} diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 9e119cd28a..de8ee5f49c 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -1,167 +1,168 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using umbraco.interfaces; - -namespace Umbraco.Core.Services -{ - /// - /// Defines the DataType Service, which is an easy access to operations involving - /// - public interface IDataTypeService : IService - { - Attempt> CreateContainer(int parentId, string name, int userId = 0); - Attempt SaveContainer(EntityContainer container, int userId = 0); - EntityContainer GetContainer(int containerId); - EntityContainer GetContainer(Guid containerId); - IEnumerable GetContainers(string folderName, int level); - IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); - IEnumerable GetContainers(int[] containerIds); - Attempt DeleteContainer(int containerId, int userId = 0); - - /// - /// Gets a by its Name - /// - /// Name of the - /// - IDataTypeDefinition GetDataTypeDefinitionByName(string name); - - /// - /// Gets a by its Id - /// - /// Id of the - /// - IDataTypeDefinition GetDataTypeDefinitionById(int id); - - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - IDataTypeDefinition GetDataTypeDefinitionById(Guid id); - - /// - /// Gets all objects or those with the ids passed in - /// - /// Optional array of Ids - /// An enumerable list of objects - IEnumerable GetAllDataTypeDefinitions(params int[] ids); - - /// - /// Saves an - /// - /// to save - /// Id of the user issueing the save - void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0); - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - void Save(IEnumerable dataTypeDefinitions, int userId = 0); - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - /// Boolean indicating whether or not to raise events - void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); - - /// - /// Deletes an - /// - /// - /// Please note that deleting a will remove - /// all the data that references this . - /// - /// to delete - /// Id of the user issueing the deletion - void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0); - - /// - /// Gets the specified by it's unique ID - /// - /// Id of the DataType, which corresponds to the Guid Id of the control - /// object - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - IDataType GetDataTypeById(Guid id); - - /// - /// Gets a complete list of all registered 's - /// - /// An enumerable list of objects - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - IEnumerable GetAllDataTypes(); - - /// - /// Gets a by its control Id - /// - /// Id of the DataType control - /// - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - IEnumerable GetDataTypeDefinitionByControlId(Guid id); - - /// - /// Gets a by its control Id - /// - /// Alias of the property editor - /// Collection of objects with a matching contorl id - IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias); - - /// - /// Gets all values for an - /// - /// Id of the to retrieve prevalues from - /// An enumerable list of string values - IEnumerable GetPreValuesByDataTypeId(int id); - - /// - /// Gets a pre-value collection by data type id - /// - /// - /// - PreValueCollection GetPreValuesCollectionByDataTypeId(int id); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of string values to save - [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - void SavePreValues(int dataTypeId, IEnumerable values); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of key/value pairs to save - void SavePreValues(int dataTypeId, IDictionary values); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// The DataTypeDefinition to save PreValues for - /// List of key/value pairs to save - void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values); - - /// - /// Saves the data type and it's prevalues - /// - /// - /// - /// - void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0); - - /// - /// Gets a specific PreValue by its Id - /// - /// Id of the PreValue to retrieve the value from - /// PreValue as a string - string GetPreValueAsString(int id); - - Attempt> Move(IDataTypeDefinition toMove, int parentId); - - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using umbraco.interfaces; + +namespace Umbraco.Core.Services +{ + /// + /// Defines the DataType Service, which is an easy access to operations involving + /// + public interface IDataTypeService : IService + { + Attempt> CreateContainer(int parentId, string name, int userId = 0); + Attempt SaveContainer(EntityContainer container, int userId = 0); + EntityContainer GetContainer(int containerId); + EntityContainer GetContainer(Guid containerId); + IEnumerable GetContainers(string folderName, int level); + IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); + IEnumerable GetContainers(int[] containerIds); + Attempt DeleteContainer(int containerId, int userId = 0); + + /// + /// Gets a by its Name + /// + /// Name of the + /// + IDataTypeDefinition GetDataTypeDefinitionByName(string name); + + /// + /// Gets a by its Id + /// + /// Id of the + /// + IDataTypeDefinition GetDataTypeDefinitionById(int id); + + /// + /// Gets a by its unique guid Id + /// + /// Unique guid Id of the DataType + /// + IDataTypeDefinition GetDataTypeDefinitionById(Guid id); + + /// + /// Gets all objects or those with the ids passed in + /// + /// Optional array of Ids + /// An enumerable list of objects + IEnumerable GetAllDataTypeDefinitions(params int[] ids); + + /// + /// Saves an + /// + /// to save + /// Id of the user issueing the save + void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0); + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + void Save(IEnumerable dataTypeDefinitions, int userId = 0); + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); + + /// + /// Deletes an + /// + /// + /// Please note that deleting a will remove + /// all the data that references this . + /// + /// to delete + /// Id of the user issueing the deletion + void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0); + + /// + /// Gets the specified by it's unique ID + /// + /// Id of the DataType, which corresponds to the Guid Id of the control + /// object + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + IDataType GetDataTypeById(Guid id); + + /// + /// Gets a complete list of all registered 's + /// + /// An enumerable list of objects + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + IEnumerable GetAllDataTypes(); + + /// + /// Gets a by its control Id + /// + /// Id of the DataType control + /// + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + IEnumerable GetDataTypeDefinitionByControlId(Guid id); + + /// + /// Gets a by its control Id + /// + /// Alias of the property editor + /// Collection of objects with a matching contorl id + IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias); + + /// + /// Gets all values for an + /// + /// Id of the to retrieve prevalues from + /// An enumerable list of string values + IEnumerable GetPreValuesByDataTypeId(int id); + + /// + /// Gets a pre-value collection by data type id + /// + /// + /// + PreValueCollection GetPreValuesCollectionByDataTypeId(int id); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of string values to save + [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] + void SavePreValues(int dataTypeId, IEnumerable values); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(int dataTypeId, IDictionary values); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// The DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values); + + /// + /// Saves the data type and it's prevalues + /// + /// + /// + /// + void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0); + + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string + string GetPreValueAsString(int id); + + Attempt> Move(IDataTypeDefinition toMove, int parentId); + + bool IsDataTypeIgnoringUserStartNodes(Guid dataTypeId); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index b81e62a66b..3b6fb5f9cd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,7 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", - ignoreUserStartNodes: "@", + datatypeId: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -62,10 +62,10 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } - //append ignoreUserStartNodes value if there is one - if (scope.ignoreUserStartNodes) { - searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes; - } + //append dataTypeId value if there is one + if (scope.datatypeId) { + searchArgs["dataTypeId"] = scope.datatypeId; + } searcher(searchArgs).then(function (data) { scope.searchCallback(data); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 77e587bcc6..31eccfb5aa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -19,8 +19,8 @@ * contentResource.getById(1234) * .then(function(data) { * $scope.content = data; - * }); - * + * }); + * **/ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { @@ -83,7 +83,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * $scope.complete = true; * }); - * + * * @param {Object} args arguments object * @param {Int} args.parentId the ID of the parent node * @param {Array} options.sortedIds array of node IDs as they should be sorted @@ -124,9 +124,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was moved"); * }, function(err){ - * alert("node didnt move:" + err.data.Message); + * alert("node didnt move:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.idd the ID of the node to move * @param {Int} args.parentId the ID of the parent node to move to @@ -167,9 +167,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was copied"); * }, function(err){ - * alert("node wasnt copy:" + err.data.Message); + * alert("node wasnt copy:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.id the ID of the node to copy * @param {Int} args.parentId the ID of the parent node to copy to @@ -208,9 +208,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was unpulished"); * }, function(err){ - * alert("node wasnt unpublished:" + err.data.Message); + * alert("node wasnt unpublished:" + err.data.Message); * }); - * + * * @param {Int} id the ID of the node to unpublish * @returns {Promise} resourcePromise object. * @@ -242,8 +242,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its empty!'); * }); - * - * + * + * * @returns {Promise} resourcePromise object. * */ @@ -270,9 +270,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its gone!'); * }); - * - * - * @param {Int} id id of content item to delete + * + * + * @param {Int} id id of content item to delete * @returns {Promise} resourcePromise object. * */ @@ -308,20 +308,20 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
               * contentResource.getById(1234)
               *    .then(function(content) {
    -          *        var myDoc = content; 
    +          *        var myDoc = content;
               *        alert('its here!');
               *    });
    -          * 
    - * + * + * * @param {Int} id id of content item to return * @param {Object} options optional options object - * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to + * @param {Guid} options.dataTypeId set to determine whether to allow a user to choose nodes that they normally don't have access to * @returns {Promise} resourcePromise object containing the content item. * */ getById: function (id, options) { var defaults = { - ignoreUserStartNodes: false + dataTypeId: null }; if (options === undefined) { options = {}; @@ -336,7 +336,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), + [{ id: id }, { dataTypeId: options.dataTypeId }])), 'Failed to retrieve data for content id ' + id); }, @@ -385,12 +385,12 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
               * contentResource.getByIds( [1234,2526,28262])
               *    .then(function(contentArray) {
    -          *        var myDoc = contentArray; 
    +          *        var myDoc = contentArray;
               *        alert('they are here!');
               *    });
    -          * 
    - * - * @param {Array} ids ids of content items to return as an array + * + * + * @param {Array} ids ids of content items to return as an array * @returns {Promise} resourcePromise object containing the content items array. * */ @@ -418,28 +418,28 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * + * * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * * The scaffold is used to build editors for content that has not yet been populated with data. - * + * * ##usage *
               * contentResource.getScaffold(1234, 'homepage')
               *    .then(function(scaffold) {
               *        var myDoc = scaffold;
    -          *        myDoc.name = "My new document"; 
    +          *        myDoc.name = "My new document";
               *
               *        contentResource.publish(myDoc, true)
               *            .then(function(content){
               *                alert("Retrieved, updated and published again");
               *            });
               *    });
    -          * 
    - * + * + * * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on + * @param {String} alias contenttype alias to base the scaffold on * @returns {Promise} resourcePromise object containing the content scaffold. * */ @@ -479,8 +479,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function(url) { * alert('its here!'); * }); - * - * + * + * * @param {Int} id Id of node to return the public url to * @returns {Promise} resourcePromise object containing the url. * @@ -506,11 +506,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
               * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
               *    .then(function(contentArray) {
    -          *        var children = contentArray; 
    +          *        var children = contentArray;
               *        alert('they are here!');
               *    });
    -          * 
    - * + * + * * @param {Int} parentid id of content item to return children of * @param {Object} options optional options object * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 @@ -594,10 +594,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('You are allowed to publish this item'); * }); - * + * * * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete + * @param {Int} id id of content item to delete * @returns {Promise} resourcePromise object. * */ @@ -637,9 +637,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
               * contentResource.getById(1234)
    @@ -650,11 +650,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
               *                alert("Retrieved, updated and saved again");
               *            });
               *    });
    -          * 
    - * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document * @returns {Promise} resourcePromise object containing the saved content item. * */ @@ -679,9 +679,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
               * contentResource.getById(1234)
    @@ -692,11 +692,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
               *                alert("Retrieved, updated and published again");
               *            });
               *    });
    -          * 
    - * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document * @returns {Promise} resourcePromise object containing the saved content item. * */ @@ -715,7 +715,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a content item, and notifies any subscribers about a pending publication - * + * * ##usage *
               * contentResource.getById(1234)
    @@ -726,11 +726,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
               *                alert("Retrieved, updated and notication send off");
               *            });
               *    });
    -          * 
    - * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document * @returns {Promise} resourcePromise object containing the saved content item. * */ @@ -748,15 +748,15 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Publishes a content item with a given ID - * + * * ##usage *
               * contentResource.publishById(1234)
               *    .then(function(content) {
               *        alert("published");
               *    });
    -          * 
    - * + * + * * @param {Int} id The ID of the conten to publish * @returns {Promise} resourcePromise object containing the published content item. * diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 4875491dc6..fff4ddde37 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -1,584 +1,584 @@ -/** - * @ngdoc service - * @name umbraco.resources.entityResource - * @description Loads in basic data for all entities - * - * ##What is an entity? - * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most - * basic properties used to display the item in trees, lists and navigation. - * - * ##What is the difference between entity and content/media/etc...? - * the entity only contains the basic node data, name, id and guid, whereas content - * nodes fetched through the content service also contains additional all of the content property data, etc.. - * This is the same principal for all entity types. Any user that is logged in to the back office will have access - * to view the basic entity information for all entities since the basic entity information does not contain sensitive information. - * - * ##Entity object types? - * You need to specify the type of object you want returned. - * - * The core object types are: - * - * - Document - * - Media - * - Member - * - Template - * - DocumentType - * - MediaType - * - MemberType - * - Macro - * - User - * - Language - * - Domain - * - DataType - **/ -function entityResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - getSafeAlias: function (value, camelCase) { - - if (!value) { - return ""; - } - value = value.replace("#", ""); - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetSafeAlias", { value: value, camelCase: camelCase })), - 'Failed to retrieve content type scaffold'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPath - * @methodOf umbraco.resources.entityResource - * - * @description - * Returns a path, given a node ID and type - * - * ##usage - *
    -         * entityResource.getPath(id, type)
    -         *    .then(function(pathArray) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the url. - * - */ - getPath: function (id, type) { - - if (id === -1 || id === "-1") { - return "-1"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPath", - [{ id: id }, {type: type }])), - 'Failed to retrieve path for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getUrl - * @methodOf umbraco.resources.entityResource - * - * @description - * Returns a url, given a node ID and type - * - * ##usage - *
    -         * entityResource.getUrl(id, type)
    -         *    .then(function(url) {
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the url. - * - */ - getUrl: function (id, type) { - - if (id === -1 || id === "-1") { - return ""; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetUrl", - [{ id: id }, {type: type }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getById - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity with a given id - * - * ##usage - *
    -         * //get media by id
    -         * entityResource.getEntityById(0, "Media")
    -         *    .then(function(ent) {
    -         *        var myDoc = ent; 
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {Int} id id of entity to return - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getById: function (id, type) { - - if (id === -1 || id === "-1") { - return null; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetById", - [{ id: id }, { type: type }])), - 'Failed to retrieve entity data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getByIds - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities, given a collection of ids - * - * ##usage - *
    -         * //Get templates for ids
    -         * entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
    -         *    .then(function(templateArray) {
    -         *        var myDoc = contentArray; 
    -         *        alert('they are here!');
    -         *    });
    -         * 
    - * - * @param {Array} ids ids of entities to return as an array - * @param {string} type type name - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - getByIds: function (ids, type) { - - var query = "type=" + type; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByIds", - query), - { - ids: ids - }), - 'Failed to retrieve entity data for ids ' + ids); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getByQuery - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity from a given xpath - * - * ##usage - *
    -         * //get content by xpath
    -         * entityResource.getByQuery("$current", -1, "Document")
    -         *    .then(function(ent) {
    -         *        var myDoc = ent; 
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {string} query xpath to use in query - * @param {Int} nodeContextId id id to start from - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getByQuery: function (query, nodeContextId, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByQuery", - [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), - 'Failed to retrieve entity data for query ' + query); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getAll - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity with a given id - * - * ##usage - *
    -         *
    -         * //Only return media
    -         * entityResource.getAll("Media")
    -         *    .then(function(ent) {
    -         *        var myDoc = ent; 
    -         *        alert('its here!');
    -         *    });
    -         * 
    - * - * @param {string} type Object type name - * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server - * @param {string} postFilterParams optional parameters for the postFilter expression - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getAll: function (type, postFilter, postFilterParams) { - - //need to build the query string manually - var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); - if (postFilter && postFilterParams) { - var counter = 0; - _.each(postFilterParams, function(val, key) { - query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; - counter++; - }); - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAll", - query)), - 'Failed to retrieve entity data for type ' + type); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getAncestors - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets ancestor entities for a given item - * - * - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getAncestors: function (id, type, options) { - var defaults = { - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAncestors", - [ - { id: id }, - { type: type }, - { ignoreUserStartNodes: options.ignoreUserStartNodes } - ])), - 'Failed to retrieve ancestor data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getChildren - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets children entities for a given item - * - * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getChildren: function (id, type) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetChildren", - [{ id: id }, { type: type }])), - 'Failed to retrieve child data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPagedChildren - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets paged children of a content item with a given id - * - * ##usage - *
    -          * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
    -          *    .then(function(contentArray) {
    -          *        var children = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 - * @param {Int} options.pageNumber if paging data, current page index, default = 100 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getPagedChildren: function (parentId, type, options) { - - var defaults = { - pageSize: 1, - pageNumber: 100, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPagedChildren", - { - id: parentId, - type: type, - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) - } - )), - 'Failed to retrieve child data for id ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPagedDescendants - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets paged descendants of a content item with a given id - * - * ##usage - *
    -          * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
    -          *    .then(function(contentArray) {
    -          *        var children = contentArray; 
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Int} parentid id of content item to return descendants of - * @param {string} type Object type name - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 100 - * @param {Int} options.pageNumber if paging data, current page index, default = 1 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getPagedDescendants: function (parentId, type, options) { - - var defaults = { - pageSize: 100, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPagedDescendants", - { - id: parentId, - type: type, - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter), - ignoreUserStartNodes: options.ignoreUserStartNodes - } - )), - 'Failed to retrieve child data for id ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#search - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities, given a lucene query and a type - * - * ##usage - *
    -         * entityResource.search("news", "Media")
    -         *    .then(function(mediaArray) {
    -         *        var myDoc = mediaArray; 
    -         *        alert('they are here!');
    -         *    });
    -         * 
    - * - * @param {String} Query search query - * @param {String} Type type of conten to search - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - search: function (query, type, options, canceler) { - - var defaults = { - searchFrom: null, - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "Search", - { - query: query, - type: type, - searchFrom: options.searchFrom, - ignoreUserStartNodes: options.ignoreUserStartNodes - }), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#searchAll - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities from all available search indexes, given a lucene query - * - * ##usage - *
    -         * entityResource.searchAll("bob")
    -         *    .then(function(array) {
    -         *        var myDoc = array; 
    -         *        alert('they are here!');
    -         *    });
    -         * 
    - * - * @param {String} Query search query - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - searchAll: function (query, canceler) { - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "SearchAll", - [{ query: query }]), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('entityResource', entityResource); +/** + * @ngdoc service + * @name umbraco.resources.entityResource + * @description Loads in basic data for all entities + * + * ##What is an entity? + * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most + * basic properties used to display the item in trees, lists and navigation. + * + * ##What is the difference between entity and content/media/etc...? + * the entity only contains the basic node data, name, id and guid, whereas content + * nodes fetched through the content service also contains additional all of the content property data, etc.. + * This is the same principal for all entity types. Any user that is logged in to the back office will have access + * to view the basic entity information for all entities since the basic entity information does not contain sensitive information. + * + * ##Entity object types? + * You need to specify the type of object you want returned. + * + * The core object types are: + * + * - Document + * - Media + * - Member + * - Template + * - DocumentType + * - MediaType + * - MemberType + * - Macro + * - User + * - Language + * - Domain + * - DataType + **/ +function entityResource($q, $http, umbRequestHelper) { + + //the factory object returned + return { + + getSafeAlias: function (value, camelCase) { + + if (!value) { + return ""; + } + value = value.replace("#", ""); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetSafeAlias", { value: value, camelCase: camelCase })), + 'Failed to retrieve content type scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPath + * @methodOf umbraco.resources.entityResource + * + * @description + * Returns a path, given a node ID and type + * + * ##usage + *
    +         * entityResource.getPath(id, type)
    +         *    .then(function(pathArray) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getPath: function (id, type) { + + if (id === -1 || id === "-1") { + return "-1"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPath", + [{ id: id }, {type: type }])), + 'Failed to retrieve path for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getUrl + * @methodOf umbraco.resources.entityResource + * + * @description + * Returns a url, given a node ID and type + * + * ##usage + *
    +         * entityResource.getUrl(id, type)
    +         *    .then(function(url) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getUrl: function (id, type) { + + if (id === -1 || id === "-1") { + return ""; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrl", + [{ id: id }, {type: type }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getById + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity with a given id + * + * ##usage + *
    +         * //get media by id
    +         * entityResource.getEntityById(0, "Media")
    +         *    .then(function(ent) {
    +         *        var myDoc = ent;
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of entity to return + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getById: function (id, type) { + + if (id === -1 || id === "-1") { + return null; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetById", + [{ id: id }, { type: type }])), + 'Failed to retrieve entity data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getByIds + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities, given a collection of ids + * + * ##usage + *
    +         * //Get templates for ids
    +         * entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
    +         *    .then(function(templateArray) {
    +         *        var myDoc = contentArray;
    +         *        alert('they are here!');
    +         *    });
    +         * 
    + * + * @param {Array} ids ids of entities to return as an array + * @param {string} type type name + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + getByIds: function (ids, type) { + + var query = "type=" + type; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByIds", + query), + { + ids: ids + }), + 'Failed to retrieve entity data for ids ' + ids); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getByQuery + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity from a given xpath + * + * ##usage + *
    +         * //get content by xpath
    +         * entityResource.getByQuery("$current", -1, "Document")
    +         *    .then(function(ent) {
    +         *        var myDoc = ent;
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {string} query xpath to use in query + * @param {Int} nodeContextId id id to start from + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getByQuery: function (query, nodeContextId, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByQuery", + [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), + 'Failed to retrieve entity data for query ' + query); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAll + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity with a given id + * + * ##usage + *
    +         *
    +         * //Only return media
    +         * entityResource.getAll("Media")
    +         *    .then(function(ent) {
    +         *        var myDoc = ent;
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {string} type Object type name + * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server + * @param {string} postFilterParams optional parameters for the postFilter expression + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getAll: function (type, postFilter, postFilterParams) { + + //need to build the query string manually + var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); + if (postFilter && postFilterParams) { + var counter = 0; + _.each(postFilterParams, function(val, key) { + query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; + counter++; + }); + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAll", + query)), + 'Failed to retrieve entity data for type ' + type); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAncestors + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets ancestor entities for a given item + * + * + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getAncestors: function (id, type, options) { + var defaults = { + dataTypeId: null + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAncestors", + [ + { id: id }, + { type: type }, + { dataTypeId: options.dataTypeId } + ])), + 'Failed to retrieve ancestor data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets children entities for a given item + * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getChildren: function (id, type) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetChildren", + [{ id: id }, { type: type }])), + 'Failed to retrieve child data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged children of a content item with a given id + * + * ##usage + *
    +          * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray;
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 + * @param {Int} options.pageNumber if paging data, current page index, default = 100 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedChildren: function (parentId, type, options) { + + var defaults = { + pageSize: 1, + pageNumber: 100, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder" + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPagedChildren", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter) + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedDescendants + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged descendants of a content item with a given id + * + * ##usage + *
    +          * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray;
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return descendants of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 100 + * @param {Int} options.pageNumber if paging data, current page index, default = 1 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedDescendants: function (parentId, type, options) { + + var defaults = { + pageSize: 100, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + dataTypeId: null + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPagedDescendants", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter), + dataTypeId: options.dataTypeId + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#search + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities, given a lucene query and a type + * + * ##usage + *
    +         * entityResource.search("news", "Media")
    +         *    .then(function(mediaArray) {
    +         *        var myDoc = mediaArray;
    +         *        alert('they are here!');
    +         *    });
    +         * 
    + * + * @param {String} Query search query + * @param {String} Type type of conten to search + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + search: function (query, type, options, canceler) { + + var defaults = { + searchFrom: null, + dataTypeId: null + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "Search", + { + query: query, + type: type, + searchFrom: options.searchFrom, + dataTypeId: options.dataTypeId + }), + httpConfig), + 'Failed to retrieve entity data for query ' + query); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#searchAll + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities from all available search indexes, given a lucene query + * + * ##usage + *
    +         * entityResource.searchAll("bob")
    +         *    .then(function(array) {
    +         *        var myDoc = array;
    +         *        alert('they are here!');
    +         *    });
    +         * 
    + * + * @param {String} Query search query + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + searchAll: function (query, canceler) { + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "SearchAll", + [{ query: query }]), + httpConfig), + 'Failed to retrieve entity data for query ' + query); + } + + }; +} + +angular.module('umbraco.resources').factory('entityResource', entityResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index e968913047..6600aec0c7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -1,559 +1,559 @@ -/** - * @ngdoc service - * @name umbraco.resources.mediaResource - * @description Loads in data for media - **/ -function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMediaPostData(c, a); - } - }); - } - - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
    -          * var ids = [123,34533,2334,23434];
    -          * mediaResource.sort({ sortedIds: ids })
    -          *    .then(function() {
    -          *        $scope.complete = true;
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
    -          * mediaResource.move({ parentId: 1244, id: 123 })
    -          *    .then(function() {
    -          *        alert("node was moved");
    -          *    }, function(err){
    -          *      alert("node didnt move:" + err.data.Message);
    -          *    });
    -          * 
    - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - { - error: function(data){ - var errorMsg = 'Failed to move media'; - if (data.id !== undefined && data.parentId !== undefined) { - if (data.id === data.parentId) { - errorMsg = 'Media can\'t be moved into itself'; - } - } - else if (data.notifications !== undefined) { - if (data.notifications.length > 0) { - if (data.notifications[0].header.length > 0) { - errorMsg = data.notifications[0].header; - } - if (data.notifications[0].message.length > 0) { - errorMsg = errorMsg + ": " + data.notifications[0].message; - } - } - } - - return { - errorMsg: errorMsg - }; - } - }); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
    -          * mediaResource.getById(1234)
    -          *    .then(function(media) {
    -          *        var myMedia = media;
    -          *        alert('its here!');
    -          *    });
    -          * 
    - * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
    -          * mediaResource.deleteById(1234)
    -          *    .then(function() {
    -          *        alert('its gone!');
    -          *    });
    -          * 
    - * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
    -          * mediaResource.getByIds( [1234,2526,28262])
    -          *    .then(function(mediaArray) {
    -          *        var myDoc = contentArray;
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
    -          * mediaResource.getScaffold(1234, 'folder')
    -          *    .then(function(scaffold) {
    -          *        var myDoc = scaffold;
    -          *        myDoc.name = "My new media item";
    -          *
    -          *        mediaResource.save(myDoc, true)
    -          *            .then(function(media){
    -          *                alert("Retrieved, updated and saved again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); - - }, - - rootMedia: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); - - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
    -          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
    -          *    .then(function(contentArray) {
    -          *        var children = contentArray;
    -          *        alert('they are here!');
    -          *    });
    -          * 
    - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { - - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true, - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; - } - if (angular.isString(v)) { - return v === "true"; - } - if (typeof v === "boolean") { - return v; - } - return false; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter }, - { ignoreUserStartNodes: options.ignoreUserStartNodes } - ])), - 'Failed to retrieve children for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
    -          * mediaResource.getById(1234)
    -          *    .then(function(media) {
    -          *          media.name = "I want a new name!";
    -          *          mediaResource.save(media, false)
    -          *            .then(function(media){
    -          *                alert("Retrieved, updated and saved again");
    -          *            });
    -          *    });
    -          * 
    - * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (media, isNew, files) { - return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
    -          * mediaResource.addFolder("My gallery", 1234)
    -          *    .then(function(folder) {
    -          *        alert('New folder');
    -          *    });
    -          * 
    - * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ - addFolder: function (name, parentId) { - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * NOTE: This will return a max of 500 folders, if more is required it needs to be paged - * - * ##usage - *
    -          * mediaResource.getChildFolders(1234)
    -          *    .then(function(data) {
    -          *        alert('folders');
    -          *    });
    -          * 
    - * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ - getChildFolders: function (parentId) { - if (!parentId) { - parentId = -1; - } - - //NOTE: This will return a max of 500 folders, if more is required it needs to be paged - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - { - id: parentId - })), - 'Failed to retrieve child folders for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
    -          * mediaResource.emptyRecycleBin()
    -          *    .then(function() {
    -          *        alert('its empty!');
    -          *    });
    -          * 
    - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#search - * @methodOf umbraco.resources.mediaResource - * - * @description - * Paginated search for media items starting on the supplied nodeId - * - * ##usage - *
    -          * mediaResource.search("my search", 1, 100, -1)
    -          *    .then(function(searchResult) {
    -          *        alert('it's here!');
    -          *    });
    -          * 
    - * - * @param {string} query The search query - * @param {int} pageNumber The page number - * @param {int} pageSize The number of media items on a page - * @param {int} searchFrom NodeId to search from (-1 for root) - * @returns {Promise} resourcePromise object. - * - */ - search: function (query, pageNumber, pageSize, searchFrom) { - - var args = [ - { "query": query }, - { "pageNumber": pageNumber }, - { "pageSize": pageSize }, - { "searchFrom": searchFrom } - ]; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "Search", - args)), - 'Failed to retrieve media items for search: ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('mediaResource', mediaResource); +/** + * @ngdoc service + * @name umbraco.resources.mediaResource + * @description Loads in data for media + **/ +function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { + + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); + } + + return { + + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
    +          * var ids = [123,34533,2334,23434];
    +          * mediaResource.sort({ sortedIds: ids })
    +          *    .then(function() {
    +          *        $scope.complete = true;
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
    +          * mediaResource.move({ parentId: 1244, id: 123 })
    +          *    .then(function() {
    +          *        alert("node was moved");
    +          *    }, function(err){
    +          *      alert("node didnt move:" + err.data.Message);
    +          *    });
    +          * 
    + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + { + error: function(data){ + var errorMsg = 'Failed to move media'; + if (data.id !== undefined && data.parentId !== undefined) { + if (data.id === data.parentId) { + errorMsg = 'Media can\'t be moved into itself'; + } + } + else if (data.notifications !== undefined) { + if (data.notifications.length > 0) { + if (data.notifications[0].header.length > 0) { + errorMsg = data.notifications[0].header; + } + if (data.notifications[0].message.length > 0) { + errorMsg = errorMsg + ": " + data.notifications[0].message; + } + } + } + + return { + errorMsg: errorMsg + }; + } + }); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
    +          * mediaResource.getById(1234)
    +          *    .then(function(media) {
    +          *        var myMedia = media;
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
    +          * mediaResource.deleteById(1234)
    +          *    .then(function() {
    +          *        alert('its gone!');
    +          *    });
    +          * 
    + * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
    +          * mediaResource.getByIds( [1234,2526,28262])
    +          *    .then(function(mediaArray) {
    +          *        var myDoc = contentArray;
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
    +          * mediaResource.getScaffold(1234, 'folder')
    +          *    .then(function(scaffold) {
    +          *        var myDoc = scaffold;
    +          *        myDoc.name = "My new media item";
    +          *
    +          *        mediaResource.save(myDoc, true)
    +          *            .then(function(media){
    +          *                alert("Retrieved, updated and saved again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); + + }, + + rootMedia: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); + + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
    +          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray;
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true, + dataTypeId: null + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter }, + { dataTypeId: options.dataTypeId } + ])), + 'Failed to retrieve children for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
    +          * mediaResource.getById(1234)
    +          *    .then(function(media) {
    +          *          media.name = "I want a new name!";
    +          *          mediaResource.save(media, false)
    +          *            .then(function(media){
    +          *                alert("Retrieved, updated and saved again");
    +          *            });
    +          *    });
    +          * 
    + * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
    +          * mediaResource.addFolder("My gallery", 1234)
    +          *    .then(function(folder) {
    +          *        alert('New folder');
    +          *    });
    +          * 
    + * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ + addFolder: function (name, parentId) { + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * NOTE: This will return a max of 500 folders, if more is required it needs to be paged + * + * ##usage + *
    +          * mediaResource.getChildFolders(1234)
    +          *    .then(function(data) {
    +          *        alert('folders');
    +          *    });
    +          * 
    + * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function (parentId) { + if (!parentId) { + parentId = -1; + } + + //NOTE: This will return a max of 500 folders, if more is required it needs to be paged + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + { + id: parentId + })), + 'Failed to retrieve child folders for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
    +          * mediaResource.emptyRecycleBin()
    +          *    .then(function() {
    +          *        alert('its empty!');
    +          *    });
    +          * 
    + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#search + * @methodOf umbraco.resources.mediaResource + * + * @description + * Paginated search for media items starting on the supplied nodeId + * + * ##usage + *
    +          * mediaResource.search("my search", 1, 100, -1)
    +          *    .then(function(searchResult) {
    +          *        alert('it's here!');
    +          *    });
    +          * 
    + * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ + search: function (query, pageNumber, pageSize, searchFrom) { + + var args = [ + { "query": query }, + { "pageNumber": pageNumber }, + { "pageSize": pageSize }, + { "searchFrom": searchFrom } + ]; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "Search", + args)), + 'Failed to retrieve media items for search: ' + query); + } + + }; +} + +angular.module('umbraco.resources').factory('mediaResource', mediaResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 0d00678282..24b946cdc8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -1,174 +1,174 @@ -/** - * @ngdoc service - * @name umbraco.services.searchService - * - * - * @description - * Service for handling the main application search, can currently search content, media and members - * - * ##usage - * To use, simply inject the searchService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *      searchService.searchMembers({term: 'bob'}).then(function(results){
    - *          angular.forEach(results, function(result){
    - *                  //returns:
    - *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
    - *           })          
    - *           var result = 
    - *       }) 
    - * 
    - */ -angular.module('umbraco.services') - .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { - - return { - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMembers - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default member search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching members - */ - searchMembers: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom - } - - return entityResource.search(args.term, "Member", options).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMemberResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchContent - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default internal content search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching content items - */ - searchContent: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureContentResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMedia - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default media search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching media items - */ - searchMedia: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Media", options).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMediaResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchAll - * @methodOf umbraco.services.searchService - * - * @description - * Searches all available indexes and returns all results in one collection - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching items - */ - searchAll: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.searchAll(args.term, args.canceler).then(function (data) { - - _.each(data, function (resultByType) { +/** + * @ngdoc service + * @name umbraco.services.searchService + * + * + * @description + * Service for handling the main application search, can currently search content, media and members + * + * ##usage + * To use, simply inject the searchService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *      searchService.searchMembers({term: 'bob'}).then(function(results){
    + *          angular.forEach(results, function(result){
    + *                  //returns:
    + *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
    + *           })
    + *           var result =
    + *       })
    + * 
    + */ +angular.module('umbraco.services') + .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { + + return { + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMembers + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default member search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching members + */ + searchMembers: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom + } + + return entityResource.search(args.term, "Member", options).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMemberResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchContent + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default internal content search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching content items + */ + searchContent: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom, + dataTypeId: args.dataTypeId + } + + return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureContentResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMedia + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default media search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching media items + */ + searchMedia: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom, + dataTypeId: args.dataTypeId + } + + return entityResource.search(args.term, "Media", options).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMediaResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchAll + * @methodOf umbraco.services.searchService + * + * @description + * Searches all available indexes and returns all results in one collection + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching items + */ + searchAll: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.searchAll(args.term, args.canceler).then(function (data) { + + _.each(data, function (resultByType) { //we need to format the search result data to include things like the subtitle, urls, etc... - // this is done with registered angular services as part of the SearchableTreeAttribute, if that - // is not found, than we format with the default formatter + // this is done with registered angular services as part of the SearchableTreeAttribute, if that + // is not found, than we format with the default formatter var formatterMethod = searchResultFormatter.configureDefaultResult; //check if a custom formatter is specified... - if (resultByType.jsSvc) { - var searchFormatterService = $injector.get(resultByType.jsSvc); - if (searchFormatterService) { - if (!resultByType.jsMethod) { - resultByType.jsMethod = "format"; - } - formatterMethod = searchFormatterService[resultByType.jsMethod]; - - if (!formatterMethod) { - throw "The method " + resultByType.jsMethod + " on the angular service " + resultByType.jsSvc + " could not be found"; - } - } - } - //now apply the formatter for each result - _.each(resultByType.results, function (item) { - formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); + if (resultByType.jsSvc) { + var searchFormatterService = $injector.get(resultByType.jsSvc); + if (searchFormatterService) { + if (!resultByType.jsMethod) { + resultByType.jsMethod = "format"; + } + formatterMethod = searchFormatterService[resultByType.jsMethod]; + + if (!formatterMethod) { + throw "The method " + resultByType.jsMethod + " on the angular service " + resultByType.jsSvc + " could not be found"; + } + } + } + //now apply the formatter for each result + _.each(resultByType.results, function (item) { + formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); }); - - }); - - return data; - }); - - }, - - //TODO: This doesn't do anything! - setCurrent: function (sectionAlias) { - - var currentSection = sectionAlias; - } - }; - }); + + }); + + return data; + }); + + }, + + //TODO: This doesn't do anything! + setCurrent: function (sectionAlias) { + + var currentSection = sectionAlias; + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index 5d6a3bf12c..67d62de223 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -1,170 +1,172 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { - var dialogOptions = $scope.dialogOptions; - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - $scope.dialogTreeEventHandler = $({}); - $scope.target = {}; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - - //if we have a node ID, we fetch the current node to build the form data - if ($scope.target.id || $scope.target.udi) { - - var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; - - if (!$scope.target.path) { - entityResource.getPath(id, "Document").then(function (path) { - $scope.target.path = path; - //now sync the tree to this path - $scope.dialogTreeEventHandler.syncTree({ - path: $scope.target.path, - tree: "content" - }); - }); - } - - // if a link exists, get the properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); - } else if ($scope.target.url.length) { +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", + function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + var dialogOptions = $scope.dialogOptions; + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + debugger; + $scope.dialogTreeEventHandler = $({}); + $scope.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + dataTypeId: $scope.model.dataTypeId, + results: [], + selectedSearchResults: [] + } + + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + + //if we have a node ID, we fetch the current node to build the form data + if ($scope.target.id || $scope.target.udi) { + + var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; + + if (!$scope.target.path) { + entityResource.getPath(id, "Document").then(function (path) { + $scope.target.path = path; + //now sync the tree to this path + $scope.dialogTreeEventHandler.syncTree({ + path: $scope.target.path, + tree: "content" + }); + }); + } + + // if a link exists, get the properties to build the anchor name list + contentResource.getById(id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.target.url = resp.urls[0]; + }); + } else if ($scope.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? var indexOfAnchor = $scope.target.url.search(/(#|\?)/); - if (indexOfAnchor > -1) { - // populate the anchor - $scope.target.anchor = $scope.target.url.substring(indexOfAnchor); - // then rewrite the model and populate the link + if (indexOfAnchor > -1) { + // populate the anchor + $scope.target.anchor = $scope.target.url.substring(indexOfAnchor); + // then rewrite the model and populate the link $scope.target.url = $scope.target.url.substring(0, indexOfAnchor); - } - } - } - - if (dialogOptions.anchors) { - $scope.anchorValues = dialogOptions.anchors; - } - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - } else { - eventsService.emit("dialogs.linkPicker.select", args); - - if ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } - - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.target.id = args.node.id; - $scope.target.udi = args.node.udi; - $scope.target.name = args.node.name; - - if (args.node.id < 0) { - $scope.target.url = "/"; - } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); - } - - if (!angular.isUndefined($scope.target.isMedia)) { - delete $scope.target.isMedia; - } - } - } - - function nodeExpandedHandler(ev, args) { - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - //check if any of the items are list views, if so we need to add a custom - // child: A node to activate the search - if (child.metaData.isContainer) { - child.hasChildren = true; - child.children = [ - { - level: child.level + 1, - hasChildren: false, - name: searchText, - metaData: { - listViewNode: child - }, - cssClass: "icon umb-tree-icon sprTree icon-search", - cssClasses: ["not-published"] - } - ]; - } - }); - } - } - - $scope.switchToMediaPicker = function () { - userService.getCurrentUser().then(function (userData) { - dialogService.mediaPicker({ - startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], - callback: function (media) { - $scope.target.id = media.id; - $scope.target.isMedia = true; - $scope.target.name = media.name; - $scope.target.url = mediaHelper.resolveFile(media); - } - }); - }); - }; - - $scope.hideSearch = function () { - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = null; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - // method to select a search result - $scope.selectResult = function (evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { - event: evt, - node: result - }); - }; - - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - }); + } + } + } + + if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + } else { + eventsService.emit("dialogs.linkPicker.select", args); + + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } + + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.target.id = args.node.id; + $scope.target.udi = args.node.udi; + $scope.target.name = args.node.name; + + if (args.node.id < 0) { + $scope.target.url = "/"; + } else { + contentResource.getById(args.node.id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.target.url = resp.urls[0]; + }); + } + + if (!angular.isUndefined($scope.target.isMedia)) { + delete $scope.target.isMedia; + } + } + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + //check if any of the items are list views, if so we need to add a custom + // child: A node to activate the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + name: searchText, + metaData: { + listViewNode: child + }, + cssClass: "icon umb-tree-icon sprTree icon-search", + cssClasses: ["not-published"] + } + ]; + } + }); + } + } + + $scope.switchToMediaPicker = function () { + userService.getCurrentUser().then(function (userData) { + dialogService.mediaPicker({ + startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], + callback: function (media) { + $scope.target.id = media.id; + $scope.target.isMedia = true; + $scope.target.name = media.name; + $scope.target.url = mediaHelper.resolveFile(media); + } + }); + }); + }; + + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html index 3b02853127..c4ac0c2e93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html @@ -1,86 +1,87 @@ -
    - - - -
    +
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js index 844ee6e240..539cb418c4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js @@ -1,431 +1,430 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", - function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { - - var tree = null; - var dialogOptions = $scope.dialogOptions; - $scope.dialogTreeEventHandler = $({}); - $scope.section = dialogOptions.section; - $scope.treeAlias = dialogOptions.treeAlias; - $scope.multiPicker = dialogOptions.multiPicker; - $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;; - $scope.searchInfo = { - searchFromId: dialogOptions.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - //create the custom query string param for this tree - $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; - $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; - - - //min / max values - if (dialogOptions.minNumber) { - dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); - } - if (dialogOptions.maxNumber) { - dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); - } - - if (dialogOptions.section === "member") { - entityType = "Member"; - } - else if (dialogOptions.section === "media") { - entityType = "Media"; - } - - //Configures filtering - if (dialogOptions.filter) { - - dialogOptions.filterExclude = false; - dialogOptions.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else if (angular.isObject(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else { - if (dialogOptions.filter.startsWith("!")) { - dialogOptions.filterExclude = true; - dialogOptions.filter = dialogOptions.filter.substring(1); - } - - //used advanced filtering - if (dialogOptions.filter.startsWith("{")) { - dialogOptions.filterAdvanced = true; - //convert to object - dialogOptions.filter = angular.fromJson(dialogOptions.filter); - } - } - } - - function nodeExpandedHandler(ev, args) { - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - - //check if any of the items are list views, if so we need to add some custom - // children: A node to activate the search, any nodes that have already been - // selected in the search - if (child.metaData.isContainer) { - child.hasChildren = true; - child.children = [ - { - level: child.level + 1, - hasChildren: false, - parent: function () { - return child; - }, - name: searchText, - metaData: { - listViewNode: child - }, - cssClass: "icon-search", - cssClasses: ["not-published"] - } - ]; - //add base transition classes to this node - child.cssClasses.push("tree-node-slide-up"); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function(item) { - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return child; - } - }); - }); - } - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(ev, args) { - tree = args.tree; - } - - //wires up selection - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - - //add transition classes - var listViewNode = args.node.parent(); - listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); - } - else if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, function(child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - select(args.node.name, args.node.id); - - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - if ($scope.multiPicker) { - $scope.select(id); - } - else { - var node = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - $scope.submit(node); - } - } - else { - - if ($scope.multiPicker) { - $scope.select(Number(id)); - } - else { - - $scope.hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.submit(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { - $scope.submit(ent); - }); - } - } - } - } - - function performFiltering(nodes) { - - if (!dialogOptions.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, function(n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if (dialogOptions.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction(dialogOptions.filter) - ? _.filter(nodes, dialogOptions.filter) - : _.where(nodes, dialogOptions.filter); - - angular.forEach(filtered, function (value, key) { - value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - }); - } else { - var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; - - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } - - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, entityType).then(function (ents) { - $scope.submit(ents); - }); - }; - - /** method to select a search result */ - $scope.selectResult = function (evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - $scope.searchInfo.selectedSearchResults.push(result); - } - else { - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - - }; - - $scope.hideSearch = function () { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, function(c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, function(c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function (item) { - var childExists = _.find(child.children, function(c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - checkChildren(tree.root.children); - } - - - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = dialogOptions.startNodeId; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - $scope.onSearchResults = function(results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function(item) { - return !item.filtered; - }); - - $scope.searchInfo.results = results; - - //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.dialogData.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", + function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { + + var tree = null; + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + } + + //create the custom query string param for this tree + $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; + $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; + + + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + + if (dialogOptions.section === "member") { + entityType = "Member"; + } + else if (dialogOptions.section === "media") { + entityType = "Media"; + } + + //Configures filtering + if (dialogOptions.filter) { + + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else { + if (dialogOptions.filter.startsWith("!")) { + dialogOptions.filterExclude = true; + dialogOptions.filter = dialogOptions.filter.substring(1); + } + + //used advanced filtering + if (dialogOptions.filter.startsWith("{")) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + + //check if any of the items are list views, if so we need to add some custom + // children: A node to activate the search, any nodes that have already been + // selected in the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + parent: function () { + return child; + }, + name: searchText, + metaData: { + listViewNode: child + }, + cssClass: "icon-search", + cssClasses: ["not-published"] + } + ]; + //add base transition classes to this node + child.cssClasses.push("tree-node-slide-up"); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function(item) { + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return child; + } + }); + }); + } + + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + + //check filter + performFiltering(args.children); + } + } + + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + tree = args.tree; + } + + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + + //add transition classes + var listViewNode = args.node.parent(); + listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); + } + else if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + + //unselect + select(args.node.name, args.node.id); + + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function(child) { + return child.id == args.node.id; + }); + + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } + else { + eventsService.emit("dialogs.treePickerController.select", args); + + if (args.node.filtered) { + return; + } + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + } + + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + if ($scope.multiPicker) { + $scope.select(id); + } + else { + var node = { + alias: null, + icon: "icon-folder", + id: id, + name: text + }; + $scope.submit(node); + } + } + else { + + if ($scope.multiPicker) { + $scope.select(Number(id)); + } + else { + + $scope.hideSearch(); + + //if an entity has been passed in, use it + if (entity) { + $scope.submit(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, entityType).then(function (ent) { + $scope.submit(ent); + }); + } + } + } + } + + function performFiltering(nodes) { + + if (!dialogOptions.filter) { + return; + } + + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function(n) { + return !angular.isObject(n.metaData.listViewNode); + }); + + if (dialogOptions.filterAdvanced) { + + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) + ? _.filter(nodes, dialogOptions.filter) + : _.where(nodes, dialogOptions.filter); + + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, function (value, key) { + + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + + if (result.filtered) { + return; + } + + result.selected = result.selected === true ? false : true; + + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } + else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { + return i.id == result.id; + }); + } + + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + + }; + + $scope.hideSearch = function () { + + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function(c) { + return c.id == child.id; + }); + } + + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + + child.cssClasses = _.reject(child.cssClasses, function(c) { + return c === 'tree-node-slide-up-hide-active'; + }); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function(c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + + + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + $scope.onSearchResults = function(results) {; + //filter all items - this will mark an item as filtered + performFiltering(results); + + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function(item) { + return !item.filtered; + }); + + $scope.searchInfo.results = results; + + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.dialogData.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html index 4391e50c28..14bcf58f5a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html @@ -9,7 +9,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" - ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" + datatype-id="{{searchInfo.dataTypeId}}" section="content"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 79b9362d3f..8a1c3eb9a9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -18,11 +18,11 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", searchFromId: null, searchFromName: null, showSearch: false, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, results: [], selectedSearchResults: [] }; - $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; + $scope.customTreeParams = dialogOptions.dataTypeId ? "dataTypeId=" + dialogOptions.dataTypeId : ""; $scope.showTarget = $scope.model.hideTarget !== true; if (dialogOptions.currentTarget) { @@ -46,7 +46,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", }); // if a link exists, get the properties to build the anchor name list - contentResource.getById(id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + contentResource.getById(id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { $scope.model.target.url = resp.urls[0]; $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); }); @@ -88,7 +88,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", if (args.node.id < 0) { $scope.model.target.url = "/"; } else { - contentResource.getById(args.node.id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + contentResource.getById(args.node.id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { $scope.model.target.url = resp.urls[0]; $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); }); @@ -120,7 +120,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, show: true, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, submit: function (model) { var media = model.selectedImages[0]; @@ -133,7 +133,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.mediaPickerOverlay.show = false; $scope.mediaPickerOverlay = null; - // make sure the content tree has nothing highlighted + // make sure the content tree has nothing highlighted $scope.dialogTreeEventHandler.syncTree({ path: "-1", tree: "content" diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index c9f3ad54a7..4cc7b0fd46 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -43,14 +43,13 @@
    Link to page
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index e3d9326c53..e7d14c9871 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -1,426 +1,426 @@ -//used for the media picker dialog -angular.module("umbraco") - .controller("Umbraco.Overlays.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { - - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); - } - - var dialogOptions = $scope.model; - - $scope.disableFolderSelect = dialogOptions.disableFolderSelect; - $scope.onlyImages = dialogOptions.onlyImages; - $scope.showDetails = dialogOptions.showDetails; - $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; - $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.cropSize = dialogOptions.cropSize; - $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); - $scope.lockedFolder = true; - - var userStartNodes = []; - - var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; - var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); - if ($scope.onlyImages) { - $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); - } else { - // Use whitelist of allowed file types if provided - if (allowedUploadFiles !== '') { - $scope.acceptedFileTypes = allowedUploadFiles; - } else { - // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); - } - } - - $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; - - $scope.model.selectedImages = []; - - $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes - }; - - //preload selected item - $scope.target = undefined; - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - } - - function onInit() { - userService.getCurrentUser().then(function (userData) { - userStartNodes = userData.startMediaIds; - - if ($scope.startNodeId !== -1) { - entityResource.getById($scope.startNodeId, "media") - .then(function (ent) { - $scope.startNodeId = ent.id; - run(); - }); - } else { - run(); - } - }); - } - - function run() { - //default root item - if (!$scope.target) { - if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { - entityResource.getById($scope.lastOpenedNode, "media") - .then(ensureWithinStartNode, gotoStartNode); - } else { - gotoStartNode(); - } - } else { - //if a target is specified, go look it up - generally this target will just contain ids not the actual full - //media object so we need to look it up - var id = $scope.target.udi ? $scope.target.udi : $scope.target.id - var altText = $scope.target.altText; - if (id) { - mediaResource.getById(id) - .then(function (node) { - $scope.target = node; - if (ensureWithinStartNode(node)) { - selectImage(node); - $scope.target.url = mediaHelper.resolveFile(node); - $scope.target.altText = altText; - $scope.openDetailsDialog(); - } - }, - gotoStartNode); - } else { - gotoStartNode(); - } - } - } - - $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").click(); - }; - - $scope.dragLeave = function(el, event) { - $scope.activeDrag = false; - }; - - $scope.dragEnter = function(el, event) { - $scope.activeDrag = true; - }; - - $scope.submitFolder = function() { - if ($scope.newFolderName) { - $scope.creatingFolder = true; - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent - }); - $scope.creatingFolder = false; - $scope.gotoFolder(data); - $scope.showFolderInput = false; - $scope.newFolderName = ""; - }); - } else { - $scope.showFolderInput = false; - } - }; - - $scope.enterSubmitFolder = function(event) { - if (event.keyCode === 13) { - $scope.submitFolder(); - event.stopPropagation(); - } - }; - - $scope.gotoFolder = function(folder) { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - - if (!folder) { - folder = { id: -1, name: "Media", icon: "icon-folder" }; - } - - if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media", { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) - .then(function(anc) { - $scope.path = _.filter(anc, - function(f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); - }); - - } else { - $scope.path = []; - } - - mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function (types) { - $scope.acceptedMediatypes = types; - }); - - $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; - - $scope.currentFolder = folder; - localStorageService.set("umbLastOpenedMediaNodeId", folder.id); - return getChildren(folder.id); - }; - - $scope.clickHandler = function(image, event, index) { - if (image.isFolder) { - if ($scope.disableFolderSelect) { - $scope.gotoFolder(image); - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - selectImage(image); - } - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - if ($scope.showDetails) { - - $scope.target = image; - - // handle both entity and full media object - if (image.image) { - $scope.target.url = image.image; - } else { - $scope.target.url = mediaHelper.resolveFile(image); - } - - $scope.openDetailsDialog(); - } else { - selectImage(image); - } - } - }; - - $scope.clickItemName = function(item) { - if (item.isFolder) { - $scope.gotoFolder(item); - } - }; - - function selectImage(image) { - if (image.selected) { - for (var i = 0; $scope.model.selectedImages.length > i; i++) { - var imageInSelection = $scope.model.selectedImages[i]; - if (image.key === imageInSelection.key) { - image.selected = false; - $scope.model.selectedImages.splice(i, 1); - } - } - } else { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - image.selected = true; - $scope.model.selectedImages.push(image); - } - } - - function deselectAllImages(images) { - for (var i = 0; i < images.length; i++) { - var image = images[i]; - image.selected = false; - } - images.length = 0; - } - - $scope.onUploadComplete = function(files) { - $scope.gotoFolder($scope.currentFolder).then(function() { - if (files.length === 1 && $scope.model.selectedImages.length === 0) { - var image = $scope.images[$scope.images.length - 1]; - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - selectImage(image); - } - }); - }; - - $scope.onFilesQueue = function() { - $scope.activeDrag = false; - }; - - function ensureWithinStartNode(node) { - // make sure that last opened node is on the same path as start node - var nodePath = node.path.split(","); - - // also make sure the node is not trashed - if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { - $scope.gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); - return true; - } else { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - return false; - } - } - - function hasFolderAccess(node) { - var nodePath = node.path ? node.path.split(',') : [node.id]; - - for (var i = 0; i < nodePath.length; i++) { - if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) - return true; - } - - return false; - } - - function gotoStartNode(err) { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - - $scope.openDetailsDialog = function() { - - $scope.mediaPickerDetailsOverlay = {}; - $scope.mediaPickerDetailsOverlay.show = true; - - $scope.mediaPickerDetailsOverlay.submit = function(model) { - $scope.model.selectedImages.push($scope.target); - $scope.model.submit($scope.model); - - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - }; - - var debounceSearchMedia = _.debounce(function() { - $scope.$apply(function() { - if ($scope.searchOptions.filter) { - searchMedia(); - } else { - // reset pagination - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes - }; - getChildren($scope.currentFolder.id); - } - }); - }, 500); - - $scope.changeSearch = function() { - $scope.loading = true; - debounceSearchMedia(); - }; - - $scope.toggle = function() { - // Make sure to activate the changeSearch function everytime the toggle is clicked - $scope.changeSearch(); - } - - $scope.changePagination = function(pageNumber) { - $scope.loading = true; - $scope.searchOptions.pageNumber = pageNumber; - searchMedia(); - }; - - function searchMedia() { - $scope.loading = true; - entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) - .then(function(data) { - // update image data to work with image grid - angular.forEach(data.items, - function(mediaItem) { - // set thumbnail and src - mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); - mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); - // set properties to match a media object - mediaItem.properties = []; - if (mediaItem.metaData) { - if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) { - mediaItem.properties.push({ - alias: "umbracoWidth", - value: mediaItem.metaData.umbracoWidth.Value - }); - mediaItem.properties.push({ - alias: "umbracoHeight", - value: mediaItem.metaData.umbracoHeight.Value - }); - } - if (mediaItem.metaData.umbracoFile) { - mediaItem.properties.push({ - alias: "umbracoFile", - editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias, - value: mediaItem.metaData.umbracoFile.Value - }); - } - } - }); - // update images - $scope.images = data.items ? data.items : []; - // update pagination - if (data.pageNumber > 0) - $scope.searchOptions.pageNumber = data.pageNumber; - if (data.pageSize > 0) - $scope.searchOptions.pageSize = data.pageSize; - $scope.searchOptions.totalItems = data.totalItems; - $scope.searchOptions.totalPages = data.totalPages; - // set already selected images to selected - preSelectImages(); - $scope.loading = false; - }); - } - - function getChildren(id) { - $scope.loading = true; - return mediaResource.getChildren(id, { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) - .then(function(data) { - $scope.searchOptions.filter = ""; - $scope.images = data.items ? data.items : []; - // set already selected images to selected - preSelectImages(); - $scope.loading = false; - }); - } - - function preSelectImages() { - for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { - var folderImage = $scope.images[folderImageIndex]; - var imageIsSelected = false; - - if ($scope.model && angular.isArray($scope.model.selectedImages)) { - for (var selectedImageIndex = 0; - selectedImageIndex < $scope.model.selectedImages.length; - selectedImageIndex++) { - var selectedImage = $scope.model.selectedImages[selectedImageIndex]; - - if (folderImage.key === selectedImage.key) { - imageIsSelected = true; - } - } - } - - if (imageIsSelected) { - folderImage.selected = true; - } - } - } - - onInit(); - - }); +//used for the media picker dialog +angular.module("umbraco") + .controller("Umbraco.Overlays.MediaPickerController", + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { + + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); + } + + var dialogOptions = $scope.model; + + $scope.disableFolderSelect = dialogOptions.disableFolderSelect; + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); + $scope.lockedFolder = true; + + var userStartNodes = []; + + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; + var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if ($scope.onlyImages) { + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); + } else { + // Use whitelist of allowed file types if provided + if (allowedUploadFiles !== '') { + $scope.acceptedFileTypes = allowedUploadFiles; + } else { + // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } + } + + $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; + + $scope.model.selectedImages = []; + + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + + //preload selected item + $scope.target = undefined; + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + } + + function onInit() { + userService.getCurrentUser().then(function (userData) { + userStartNodes = userData.startMediaIds; + + if ($scope.startNodeId !== -1) { + entityResource.getById($scope.startNodeId, "media") + .then(function (ent) { + $scope.startNodeId = ent.id; + run(); + }); + } else { + run(); + } + }); + } + + function run() { + //default root item + if (!$scope.target) { + if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { + entityResource.getById($scope.lastOpenedNode, "media") + .then(ensureWithinStartNode, gotoStartNode); + } else { + gotoStartNode(); + } + } else { + //if a target is specified, go look it up - generally this target will just contain ids not the actual full + //media object so we need to look it up + var id = $scope.target.udi ? $scope.target.udi : $scope.target.id + var altText = $scope.target.altText; + if (id) { + mediaResource.getById(id) + .then(function (node) { + $scope.target = node; + if (ensureWithinStartNode(node)) { + selectImage(node); + $scope.target.url = mediaHelper.resolveFile(node); + $scope.target.altText = altText; + $scope.openDetailsDialog(); + } + }, + gotoStartNode); + } else { + gotoStartNode(); + } + } + } + + $scope.upload = function(v) { + angular.element(".umb-file-dropzone-directive .file-select").click(); + }; + + $scope.dragLeave = function(el, event) { + $scope.activeDrag = false; + }; + + $scope.dragEnter = function(el, event) { + $scope.activeDrag = true; + }; + + $scope.submitFolder = function() { + if ($scope.newFolderName) { + $scope.creatingFolder = true; + mediaResource + .addFolder($scope.newFolderName, $scope.currentFolder.id) + .then(function(data) { + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); + $scope.creatingFolder = false; + $scope.gotoFolder(data); + $scope.showFolderInput = false; + $scope.newFolderName = ""; + }); + } else { + $scope.showFolderInput = false; + } + }; + + $scope.enterSubmitFolder = function(event) { + if (event.keyCode === 13) { + $scope.submitFolder(); + event.stopPropagation(); + } + }; + + $scope.gotoFolder = function(folder) { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + + if (!folder) { + folder = { id: -1, name: "Media", icon: "icon-folder" }; + } + + if (folder.id > 0) { + entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) + .then(function(anc) { + $scope.path = _.filter(anc, + function(f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + + } else { + $scope.path = []; + } + + mediaTypeHelper.getAllowedImagetypes(folder.id) + .then(function (types) { + $scope.acceptedMediatypes = types; + }); + + $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; + + $scope.currentFolder = folder; + localStorageService.set("umbLastOpenedMediaNodeId", folder.id); + return getChildren(folder.id); + }; + + $scope.clickHandler = function(image, event, index) { + if (image.isFolder) { + if ($scope.disableFolderSelect) { + $scope.gotoFolder(image); + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + selectImage(image); + } + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + if ($scope.showDetails) { + + $scope.target = image; + + // handle both entity and full media object + if (image.image) { + $scope.target.url = image.image; + } else { + $scope.target.url = mediaHelper.resolveFile(image); + } + + $scope.openDetailsDialog(); + } else { + selectImage(image); + } + } + }; + + $scope.clickItemName = function(item) { + if (item.isFolder) { + $scope.gotoFolder(item); + } + }; + + function selectImage(image) { + if (image.selected) { + for (var i = 0; $scope.model.selectedImages.length > i; i++) { + var imageInSelection = $scope.model.selectedImages[i]; + if (image.key === imageInSelection.key) { + image.selected = false; + $scope.model.selectedImages.splice(i, 1); + } + } + } else { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + image.selected = true; + $scope.model.selectedImages.push(image); + } + } + + function deselectAllImages(images) { + for (var i = 0; i < images.length; i++) { + var image = images[i]; + image.selected = false; + } + images.length = 0; + } + + $scope.onUploadComplete = function(files) { + $scope.gotoFolder($scope.currentFolder).then(function() { + if (files.length === 1 && $scope.model.selectedImages.length === 0) { + var image = $scope.images[$scope.images.length - 1]; + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + selectImage(image); + } + }); + }; + + $scope.onFilesQueue = function() { + $scope.activeDrag = false; + }; + + function ensureWithinStartNode(node) { + // make sure that last opened node is on the same path as start node + var nodePath = node.path.split(","); + + // also make sure the node is not trashed + if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { + $scope.gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); + return true; + } else { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + return false; + } + } + + function hasFolderAccess(node) { + var nodePath = node.path ? node.path.split(',') : [node.id]; + + for (var i = 0; i < nodePath.length; i++) { + if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) + return true; + } + + return false; + } + + function gotoStartNode(err) { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + } + + $scope.openDetailsDialog = function() { + + $scope.mediaPickerDetailsOverlay = {}; + $scope.mediaPickerDetailsOverlay.show = true; + + $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.model.selectedImages.push($scope.target); + $scope.model.submit($scope.model); + + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + + $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + }; + + var debounceSearchMedia = _.debounce(function() { + $scope.$apply(function() { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); + + $scope.changeSearch = function() { + $scope.loading = true; + debounceSearchMedia(); + }; + + $scope.toggle = function() { + // Make sure to activate the changeSearch function everytime the toggle is clicked + $scope.changeSearch(); + } + + $scope.changePagination = function(pageNumber) { + $scope.loading = true; + $scope.searchOptions.pageNumber = pageNumber; + searchMedia(); + }; + + function searchMedia() { + $scope.loading = true; + entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) + .then(function(data) { + // update image data to work with image grid + angular.forEach(data.items, + function(mediaItem) { + // set thumbnail and src + mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); + mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); + // set properties to match a media object + mediaItem.properties = []; + if (mediaItem.metaData) { + if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) { + mediaItem.properties.push({ + alias: "umbracoWidth", + value: mediaItem.metaData.umbracoWidth.Value + }); + mediaItem.properties.push({ + alias: "umbracoHeight", + value: mediaItem.metaData.umbracoHeight.Value + }); + } + if (mediaItem.metaData.umbracoFile) { + mediaItem.properties.push({ + alias: "umbracoFile", + editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias, + value: mediaItem.metaData.umbracoFile.Value + }); + } + } + }); + // update images + $scope.images = data.items ? data.items : []; + // update pagination + if (data.pageNumber > 0) + $scope.searchOptions.pageNumber = data.pageNumber; + if (data.pageSize > 0) + $scope.searchOptions.pageSize = data.pageSize; + $scope.searchOptions.totalItems = data.totalItems; + $scope.searchOptions.totalPages = data.totalPages; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + + function getChildren(id) { + $scope.loading = true; + return mediaResource.getChildren(id, { dataTypeId: $scope.model.dataTypeId }) + .then(function(data) { + $scope.searchOptions.filter = ""; + $scope.images = data.items ? data.items : []; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + + function preSelectImages() { + for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { + var folderImage = $scope.images[folderImageIndex]; + var imageIsSelected = false; + + if ($scope.model && angular.isArray($scope.model.selectedImages)) { + for (var selectedImageIndex = 0; + selectedImageIndex < $scope.model.selectedImages.length; + selectedImageIndex++) { + var selectedImage = $scope.model.selectedImages[selectedImageIndex]; + + if (folderImage.key === selectedImage.key) { + imageIsSelected = true; + } + } + } + + if (imageIsSelected) { + folderImage.selected = true; + } + } + } + + onInit(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index e1ce332b48..b4dd34dfff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -16,7 +16,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", searchFromId: dialogOptions.startNodeId, searchFromName: null, showSearch: false, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, results: [], selectedSearchResults: [] } @@ -138,8 +138,8 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", if (dialogOptions.startNodeId) params.push("startNodeId=" + dialogOptions.startNodeId); - if (dialogOptions.ignoreUserStartNodes) - params.push("ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes); + if (dialogOptions.dataTypeId) + params.push("dataTypeId=" + dialogOptions.dataTypeId); if (dialogOptions.customTreeParams) params.push(dialogOptions.customTreeParams); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index c338e3402c..cfcbcfac75 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -1,56 +1,56 @@ -
    - -
    - -
    - - -
    - - - - - - {{ emptyStateMessage }} - - -
    - - -
    - -
    - - - - -
    +
    + +
    + +
    + + +
    + + + + + + {{ emptyStateMessage }} + + +
    + + +
    + +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ce836a8d68..627baa3e7a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,366 +1,366 @@ -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it - -function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) { - - var unsubscribe; - - function subscribe() { - unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - }); - } - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); - return str.replace(rgxtrim, ''); - } - - function startWatch() { - //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required - // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable - // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. - // In their source code there is no event so we need to just subscribe to our model changes here. - //This also makes it easier to manage models, we update one and the rest will just work. - $scope.$watch(function () { - //return the joined Ids as a string to watch - return _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }).join(); - }, function (newVal) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - - //Validate! - if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { - $scope.contentPickerForm.minCount.$setValidity("minCount", false); - } - else { - $scope.contentPickerForm.minCount.$setValidity("minCount", true); - } - - if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); - } - else { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); - } - - setSortingState($scope.renderModel); - - }); - } - - $scope.renderModel = []; - - $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; - - //the default pre-values - var defaultConfig = { - multiPicker: false, - showOpenButton: false, - showEditButton: false, - showPathOnHover: false, - ignoreUserStartNodes: false, - maxNumber: 1, - minNumber : 0, - startNode: { - query: "", - type: "content", - id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker - } - }; - - // sortable options - $scope.sortableOptions = { - axis: "y", - containment: "parent", - distance: 10, - opacity: 0.7, - tolerance: "pointer", - scroll: true, - zIndex: 6000, - update: function (e, ui) { - angularHelper.getCurrentForm($scope).$setDirty(); - } - }; - - if ($scope.model.config) { - //merge the server config on top of the default config, then set the server config to use the result - $scope.model.config = angular.extend(defaultConfig, $scope.model.config); - } - - //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! - $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); - $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); - $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); - $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); - $scope.model.config.ignoreUserStartNodes = ($scope.model.config.ignoreUserStartNodes === "1" ? true : false); - - var entityType = $scope.model.config.startNode.type === "member" - ? "Member" - : $scope.model.config.startNode.type === "media" - ? "Media" - : "Document"; - $scope.allowOpenButton = entityType === "Document"; - $scope.allowEditButton = entityType === "Document"; - $scope.allowRemoveButton = true; - - //the dialog options for the picker - var dialogOptions = { - multiPicker: $scope.model.config.multiPicker, - entityType: entityType, - filterCssClass: "not-allowed not-published", - startNodeId: null, - currentNode: editorState ? editorState.current : null, - callback: function (data) { - if (angular.isArray(data)) { - _.each(data, function (item, i) { - $scope.add(item); - }); - } else { - $scope.clear(); - $scope.add(data); - } - angularHelper.getCurrentForm($scope).$setDirty(); - }, - treeAlias: $scope.model.config.startNode.type, - section: $scope.model.config.startNode.type, - idType: "int" - }; - - //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the - // pre-value config on to the dialog options - angular.extend(dialogOptions, $scope.model.config); - - //We need to manually handle the filter for members here since the tree displayed is different and only contains - // searchable list views - if (entityType === "Member") { - //first change the not allowed filter css class - dialogOptions.filterCssClass = "not-allowed"; - var currFilter = dialogOptions.filter; - //now change the filter to be a method - dialogOptions.filter = function(i) { - //filter out the list view nodes - if (i.metaData.isContainer) { - return true; - } - if (!currFilter) { - return false; - } - //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, - // but not much we can do about that since members require special filtering. - var filterItem = currFilter.toLowerCase().split(','); - var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; - if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { - return true; - } - - return false; - } - } - - if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { - //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query - dialogOptions.startNodeId = -1; - } else if ($scope.model.config.startNode.query) { - //if we have a query for the startnode, we will use that. - var rootId = $routeParams.id; - entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) { - dialogOptions.startNodeId = $scope.model.config.idType === "udi" ? ent.udi : ent.id; - }); - } - else { - dialogOptions.startNodeId = $scope.model.config.startNode.id; - } - - //dialog - $scope.openContentPicker = function() { - $scope.contentPickerOverlay = dialogOptions; - $scope.contentPickerOverlay.view = "treepicker"; - $scope.contentPickerOverlay.show = true; - - $scope.contentPickerOverlay.submit = function(model) { - - if (angular.isArray(model.selection)) { - _.each(model.selection, function (item, i) { - $scope.add(item); - }); - angularHelper.getCurrentForm($scope).$setDirty(); - } - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - }; - - $scope.remove = function (index) { - $scope.renderModel.splice(index, 1); - angularHelper.getCurrentForm($scope).$setDirty(); - }; - - $scope.showNode = function (index) { - var item = $scope.renderModel[index]; - var id = item.id; - var section = $scope.model.config.startNode.type.toLowerCase(); - - entityResource.getPath(id, entityType).then(function (path) { - navigationService.changeSection(section); - navigationService.showTree(section, { - tree: section, path: path, forceReload: false, activate: true - }); - var routePath = section + "/" + section + "/edit/" + id.toString(); - $location.path(routePath).search(""); - }); - } - - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - - var itemId = $scope.model.config.idType === "udi" ? item.udi : item.id; - - if (currIds.indexOf(itemId) < 0) { - setEntityUrl(item); - } - }; - - $scope.clear = function () { - $scope.renderModel = []; - }; - - $scope.openMiniEditor = function(node) { - miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){ - // update the node - node.name = updatedNode.name; - node.published = updatedNode.hasPublishedVersion; - if(entityType !== "Member") { - entityResource.getUrl(updatedNode.id, entityType).then(function(data){ - node.url = data; - }); - } - }); - }; - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - if(unsubscribe) { - unsubscribe(); - } - }); - - var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - - //load current data if anything selected - if (modelIds.length > 0) { - entityResource.getByIds(modelIds, entityType).then(function(data) { - - _.each(modelIds, - function(id, i) { - var entity = _.find(data, - function(d) { - return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); - }); - - if (entity) { - setEntityUrl(entity); - } - - }); - - //everything is loaded, start the watch on the model - startWatch(); - subscribe(); - }); - } - else { - //everything is loaded, start the watch on the model - startWatch(); - subscribe(); - } - - function setEntityUrl(entity) { - - // get url for content and media items - if(entityType !== "Member") { - entityResource.getUrl(entity.id, entityType).then(function(data){ - // update url - angular.forEach($scope.renderModel, function(item){ - if (item.id === entity.id) { - if (entity.trashed) { - item.url = localizationService.dictionary.general_recycleBin; - } else { - item.url = data; - } - } - }); - }); - } - - // add the selected item to the renderModel - // if it needs to show a url the item will get - // updated when the url comes back from server - addSelectedItem(entity); - - } - - function addSelectedItem(item) { - - // set icon - if(item.icon) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - } - - // set default icon - if (!item.icon) { - switch (entityType) { - case "Document": - item.icon = "icon-document"; - break; - case "Media": - item.icon = "icon-picture"; - break; - case "Member": - item.icon = "icon-user"; - break; - } - } - - $scope.renderModel.push({ - "name": item.name, - "id": item.id, - "udi": item.udi, - "icon": item.icon, - "path": item.path, - "url": item.url, - "trashed": item.trashed, - "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true - // only content supports published/unpublished content so we set everything else to published so the UI looks correct - }); - - } - - function setSortingState(items) { - // disable sorting if the list only consist of one item - if(items.length > 1) { - $scope.sortableOptions.disabled = false; - } else { - $scope.sortableOptions.disabled = true; - } - } - -} - -angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); +//this controller simply tells the dialogs service to open a mediaPicker window +//with a specified callback, this callback will receive an object with a selection on it + +function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) { + + var unsubscribe; + + function subscribe() { + unsubscribe = $scope.$on("formSubmitting", function (ev, args) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ","); + }); + } + + function trim(str, chr) { + var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + + function startWatch() { + //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required + // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable + // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. + // In their source code there is no event so we need to just subscribe to our model changes here. + //This also makes it easier to manage models, we update one and the rest will just work. + $scope.$watch(function () { + //return the joined Ids as a string to watch + return _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }).join(); + }, function (newVal) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ","); + + //Validate! + if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { + $scope.contentPickerForm.minCount.$setValidity("minCount", false); + } + else { + $scope.contentPickerForm.minCount.$setValidity("minCount", true); + } + + if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { + $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); + } + else { + $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); + } + + setSortingState($scope.renderModel); + + }); + } + + $scope.renderModel = []; + + $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; + + //the default pre-values + var defaultConfig = { + multiPicker: false, + showOpenButton: false, + showEditButton: false, + showPathOnHover: false, + dataTypeId: null, + maxNumber: 1, + minNumber : 0, + startNode: { + query: "", + type: "content", + id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker + } + }; + + // sortable options + $scope.sortableOptions = { + axis: "y", + containment: "parent", + distance: 10, + opacity: 0.7, + tolerance: "pointer", + scroll: true, + zIndex: 6000, + update: function (e, ui) { + angularHelper.getCurrentForm($scope).$setDirty(); + } + }; + + if ($scope.model.config) { + //merge the server config on top of the default config, then set the server config to use the result + $scope.model.config = angular.extend(defaultConfig, $scope.model.config); + } + + //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! + $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); + $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); + $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); + $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); + + var entityType = $scope.model.config.startNode.type === "member" + ? "Member" + : $scope.model.config.startNode.type === "media" + ? "Media" + : "Document"; + $scope.allowOpenButton = entityType === "Document"; + $scope.allowEditButton = entityType === "Document"; + $scope.allowRemoveButton = true; + + //the dialog options for the picker + var dialogOptions = { + multiPicker: $scope.model.config.multiPicker, + entityType: entityType, + filterCssClass: "not-allowed not-published", + startNodeId: null, + currentNode: editorState ? editorState.current : null, + callback: function (data) { + if (angular.isArray(data)) { + _.each(data, function (item, i) { + $scope.add(item); + }); + } else { + $scope.clear(); + $scope.add(data); + } + angularHelper.getCurrentForm($scope).$setDirty(); + }, + treeAlias: $scope.model.config.startNode.type, + section: $scope.model.config.startNode.type, + idType: "int" + }; + + dialogOptions.dataTypeId = $scope.model.dataTypeId; + //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the + // pre-value config on to the dialog options + angular.extend(dialogOptions, $scope.model.config); + + //We need to manually handle the filter for members here since the tree displayed is different and only contains + // searchable list views + if (entityType === "Member") { + //first change the not allowed filter css class + dialogOptions.filterCssClass = "not-allowed"; + var currFilter = dialogOptions.filter; + //now change the filter to be a method + dialogOptions.filter = function(i) { + //filter out the list view nodes + if (i.metaData.isContainer) { + return true; + } + if (!currFilter) { + return false; + } + //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, + // but not much we can do about that since members require special filtering. + var filterItem = currFilter.toLowerCase().split(','); + var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; + if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { + return true; + } + + return false; + } + } + + if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { + //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query + dialogOptions.startNodeId = -1; + } else if ($scope.model.config.startNode.query) { + //if we have a query for the startnode, we will use that. + var rootId = $routeParams.id; + entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) { + dialogOptions.startNodeId = $scope.model.config.idType === "udi" ? ent.udi : ent.id; + }); + } + else { + dialogOptions.startNodeId = $scope.model.config.startNode.id; + } + + //dialog + $scope.openContentPicker = function() { + $scope.contentPickerOverlay = dialogOptions; + $scope.contentPickerOverlay.view = "treepicker"; + $scope.contentPickerOverlay.show = true; + + $scope.contentPickerOverlay.submit = function(model) { + + if (angular.isArray(model.selection)) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + angularHelper.getCurrentForm($scope).$setDirty(); + } + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } + + }; + + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + angularHelper.getCurrentForm($scope).$setDirty(); + }; + + $scope.showNode = function (index) { + var item = $scope.renderModel[index]; + var id = item.id; + var section = $scope.model.config.startNode.type.toLowerCase(); + + entityResource.getPath(id, entityType).then(function (path) { + navigationService.changeSection(section); + navigationService.showTree(section, { + tree: section, path: path, forceReload: false, activate: true + }); + var routePath = section + "/" + section + "/edit/" + id.toString(); + $location.path(routePath).search(""); + }); + } + + $scope.add = function (item) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + + var itemId = $scope.model.config.idType === "udi" ? item.udi : item.id; + + if (currIds.indexOf(itemId) < 0) { + setEntityUrl(item); + } + }; + + $scope.clear = function () { + $scope.renderModel = []; + }; + + $scope.openMiniEditor = function(node) { + miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){ + // update the node + node.name = updatedNode.name; + node.published = updatedNode.hasPublishedVersion; + if(entityType !== "Member") { + entityResource.getUrl(updatedNode.id, entityType).then(function(data){ + node.url = data; + }); + } + }); + }; + + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + if(unsubscribe) { + unsubscribe(); + } + }); + + var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; + + //load current data if anything selected + if (modelIds.length > 0) { + entityResource.getByIds(modelIds, entityType).then(function(data) { + + _.each(modelIds, + function(id, i) { + var entity = _.find(data, + function(d) { + return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); + }); + + if (entity) { + setEntityUrl(entity); + } + + }); + + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + }); + } + else { + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + } + + function setEntityUrl(entity) { + + // get url for content and media items + if(entityType !== "Member") { + entityResource.getUrl(entity.id, entityType).then(function(data){ + // update url + angular.forEach($scope.renderModel, function(item){ + if (item.id === entity.id) { + if (entity.trashed) { + item.url = localizationService.dictionary.general_recycleBin; + } else { + item.url = data; + } + } + }); + }); + } + + // add the selected item to the renderModel + // if it needs to show a url the item will get + // updated when the url comes back from server + addSelectedItem(entity); + + } + + function addSelectedItem(item) { + + // set icon + if(item.icon) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + } + + // set default icon + if (!item.icon) { + switch (entityType) { + case "Document": + item.icon = "icon-document"; + break; + case "Media": + item.icon = "icon-picture"; + break; + case "Member": + item.icon = "icon-user"; + break; + } + } + + $scope.renderModel.push({ + "name": item.name, + "id": item.id, + "udi": item.udi, + "icon": item.icon, + "path": item.path, + "url": item.url, + "trashed": item.trashed, + "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true + // only content supports published/unpublished content so we set everything else to published so the UI looks correct + }); + + } + + function setSortingState(items) { + // disable sorting if the list only consist of one item + if(items.length > 1) { + $scope.sortableOptions.disabled = false; + } else { + $scope.sortableOptions.disabled = true; + } + } + +} + +angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index a093ccb034..c61f5419ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,11 +1,9 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, $rootScope, $timeout, userService) { - - var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - if (ignoreUserStartNodes === true) { + if ($scope.model.config.ignoreUserStartNodes === "1" ) { $scope.model.config.startNodeId = -1; $scope.model.config.startNodeIsVirtual = true; @@ -22,7 +20,7 @@ angular.module("umbraco") $scope.mediaPickerOverlay.view = "mediapicker"; $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; - $scope.mediaPickerOverlay.ignoreUserStartNodes = ignoreUserStartNodes; + $scope.mediaPickerOverlay.dataTypeId = $scope.model.dataTypeId; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index cf81300b32..e3e5d5e072 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -11,12 +11,13 @@ vm.openEmbed = openEmbed; function openLinkPicker(editor, currentTarget, anchorElement) { - + vm.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)), - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -27,12 +28,11 @@ } function openMediaPicker(editor, currentTarget, userData) { - var ignoreUserStartNodes = false; var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; var startNodeIsVirtual = userData.startMediaIds.length !== 1; + if ($scope.model.config.ignoreUserStartNodes === "1") { - ignoreUserStartNodes = true; startNodeId = -1; startNodeIsVirtual = true; } @@ -40,10 +40,10 @@ vm.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, - showDetails: true, + showDetails: true, startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, + dataTypeId: $scope.model.dataTypeId, view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 1f1305b0f0..559d0e4a19 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -1,184 +1,185 @@ -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", - function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) { - - //check the pre-values for multi-picker - var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; - var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; - var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; - var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; - - if (!$scope.model.config.startNodeId) { - if (ignoreUserStartNodes === true) { - $scope.model.config.startNodeId = -1; - $scope.model.config.startNodeIsVirtual = true; - - } else { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); - } - } - - function setupViewModel() { - $scope.mediaItems = []; - $scope.ids = []; - - $scope.isMultiPicker = multiPicker; - - if ($scope.model.value) { - var ids = $scope.model.value.split(','); - - //NOTE: We need to use the entityResource NOT the mediaResource here because - // the mediaResource has server side auth configured for which the user must have - // access to the media section, if they don't they'll get auth errors. The entityResource - // acts differently in that it allows access if the user has access to any of the apps that - // might require it's use. Therefore we need to use the metaData property to get at the thumbnail - // value. - - entityResource.getByIds(ids, "Media").then(function(medias) { - - // The service only returns item results for ids that exist (deleted items are silently ignored). - // This results in the picked items value to be set to contain only ids of picked items that could actually be found. - // Since a referenced item could potentially be restored later on, instead of changing the selected values here based - // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently - // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending - // on whether it is simply resaved or not. - // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders - // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. - - medias = _.map(ids, - function(id) { - var found = _.find(medias, - function(m) { - // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and - // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() - // compares and be completely sure it works. - return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); - }); - if (found) { - return found; - } else { - return { - name: localizationService.dictionary.mediaPicker_deletedItem, - id: $scope.model.config.idType !== "udi" ? id : null, - udi: $scope.model.config.idType === "udi" ? id : null, - icon: "icon-picture", - thumbnail: null, - trashed: true - }; - } - }); - - _.each(medias, - function(media, i) { - // if there is no thumbnail, try getting one if the media is not a placeholder item - if (!media.thumbnail && media.id && media.metaData) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } - - $scope.mediaItems.push(media); - - if ($scope.model.config.idType === "udi") { - $scope.ids.push(media.udi); - } else { - $scope.ids.push(media.id); - } - }); - - $scope.sync(); - }); - } - } - - setupViewModel(); - - $scope.remove = function(index) { - $scope.mediaItems.splice(index, 1); - $scope.ids.splice(index, 1); - $scope.sync(); - }; - - $scope.goToItem = function(item) { - $location.path('media/media/edit/' + item.id); - }; - - $scope.add = function() { - - $scope.mediaPickerOverlay = { - view: "mediapicker", - title: "Select media", - startNodeId: $scope.model.config.startNodeId, - startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, - multiPicker: multiPicker, - onlyImages: onlyImages, - disableFolderSelect: disableFolderSelect, - show: true, - submit: function(model) { - - _.each(model.selectedImages, function(media, i) { - // if there is no thumbnail, try getting one if the media is not a placeholder item - if (!media.thumbnail && media.id && media.metaData) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } - - $scope.mediaItems.push(media); - - if ($scope.model.config.idType === "udi") { - $scope.ids.push(media.udi); - } - else { - $scope.ids.push(media.id); - } - }); - - $scope.sync(); - - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; - }; - - $scope.sortableOptions = { - disabled: !$scope.isMultiPicker, - items: "li:not(.add-wrapper)", - cancel: ".unsortable", - update: function(e, ui) { - var r = []; - // TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the - // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the - // watch do all the rest. - $timeout(function(){ - angular.forEach($scope.mediaItems, function(value, key) { - r.push($scope.model.config.idType === "udi" ? value.udi : value.id); - }); - $scope.ids = r; - $scope.sync(); - }, 500, false); - } - }; - - $scope.sync = function() { - $scope.model.value = $scope.ids.join(); - }; - - $scope.showAdd = function () { - if (!multiPicker) { - if ($scope.model.value && $scope.model.value !== "") { - return false; - } - } - return true; - }; - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server - setupViewModel(); - }; - }); +//this controller simply tells the dialogs service to open a mediaPicker window +//with a specified callback, this callback will receive an object with a selection on it +angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", + function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) { + + //check the pre-values for multi-picker + var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; + var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; + var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; + + if (!$scope.model.config.startNodeId) { + + + if ( $scope.model.config.ignoreUserStartNodes === "1") { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } + } + + function setupViewModel() { + $scope.mediaItems = []; + $scope.ids = []; + + $scope.isMultiPicker = multiPicker; + + if ($scope.model.value) { + var ids = $scope.model.value.split(','); + + //NOTE: We need to use the entityResource NOT the mediaResource here because + // the mediaResource has server side auth configured for which the user must have + // access to the media section, if they don't they'll get auth errors. The entityResource + // acts differently in that it allows access if the user has access to any of the apps that + // might require it's use. Therefore we need to use the metaData property to get at the thumbnail + // value. + + entityResource.getByIds(ids, "Media").then(function(medias) { + + // The service only returns item results for ids that exist (deleted items are silently ignored). + // This results in the picked items value to be set to contain only ids of picked items that could actually be found. + // Since a referenced item could potentially be restored later on, instead of changing the selected values here based + // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently + // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending + // on whether it is simply resaved or not. + // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders + // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. + + medias = _.map(ids, + function(id) { + var found = _.find(medias, + function(m) { + // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and + // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() + // compares and be completely sure it works. + return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); + }); + if (found) { + return found; + } else { + return { + name: localizationService.dictionary.mediaPicker_deletedItem, + id: $scope.model.config.idType !== "udi" ? id : null, + udi: $scope.model.config.idType === "udi" ? id : null, + icon: "icon-picture", + thumbnail: null, + trashed: true + }; + } + }); + + _.each(medias, + function(media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + + $scope.mediaItems.push(media); + + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } else { + $scope.ids.push(media.id); + } + }); + + $scope.sync(); + }); + } + } + + setupViewModel(); + + $scope.remove = function(index) { + $scope.mediaItems.splice(index, 1); + $scope.ids.splice(index, 1); + $scope.sync(); + }; + + $scope.goToItem = function(item) { + $location.path('media/media/edit/' + item.id); + }; + + $scope.add = function() { + + $scope.mediaPickerOverlay = { + view: "mediapicker", + title: "Select media", + startNodeId: $scope.model.config.startNodeId, + startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + dataTypeId: $scope.model.dataTypeId, + multiPicker: multiPicker, + onlyImages: onlyImages, + disableFolderSelect: disableFolderSelect, + show: true, + submit: function(model) { + + _.each(model.selectedImages, function(media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + + $scope.mediaItems.push(media); + + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } + else { + $scope.ids.push(media.id); + } + }); + + $scope.sync(); + + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + }; + + $scope.sortableOptions = { + disabled: !$scope.isMultiPicker, + items: "li:not(.add-wrapper)", + cancel: ".unsortable", + update: function(e, ui) { + var r = []; + // TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the + // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the + // watch do all the rest. + $timeout(function(){ + angular.forEach($scope.mediaItems, function(value, key) { + r.push($scope.model.config.idType === "udi" ? value.udi : value.id); + }); + $scope.ids = r; + $scope.sync(); + }, 500, false); + } + }; + + $scope.sync = function() { + $scope.model.value = $scope.ids.join(); + }; + + $scope.showAdd = function () { + if (!multiPicker) { + if ($scope.model.value && $scope.model.value !== "") { + return false; + } + } + return true; + }; + + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server + setupViewModel(); + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index c7e67a0a42..124f342741 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -67,11 +67,12 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en url: link.url, target: link.target } : null; - + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: target, - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function (model) { if (model.target.url || model.target.anchor) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 6047169c54..26c6a0c051 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -1,242 +1,242 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.RelatedLinksController", - function ($rootScope, $scope, dialogService, iconHelper) { - - if (!$scope.model.value) { - $scope.model.value = []; - } - - $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE; - - $scope.newCaption = ''; - $scope.newLink = 'http://'; - $scope.newNewWindow = false; - $scope.newInternal = null; - $scope.newInternalName = ''; - $scope.newInternalIcon = null; - $scope.addExternal = true; - $scope.currentEditLink = null; - $scope.hasError = false; - - $scope.internal = function($event) { - $scope.currentEditLink = null; - - $scope.contentPickerOverlay = {}; - $scope.contentPickerOverlay.view = "contentpicker"; - $scope.contentPickerOverlay.multiPicker = false; - $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true: false; - $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; - - $scope.contentPickerOverlay.submit = function(model) { - - select(model.selection[0]); - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $event.preventDefault(); - }; - - $scope.selectInternal = function ($event, link) { - $scope.currentEditLink = link; - - $scope.contentPickerOverlay = {}; - $scope.contentPickerOverlay.view = "contentpicker"; - $scope.contentPickerOverlay.multiPicker = false; - $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; - $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; - - $scope.contentPickerOverlay.submit = function(model) { - - select(model.selection[0]); - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $event.preventDefault(); - - }; - - $scope.edit = function (idx) { - for (var i = 0; i < $scope.model.value.length; i++) { - $scope.model.value[i].edit = false; - } - $scope.model.value[idx].edit = true; - }; - - $scope.saveEdit = function (idx) { - $scope.model.value[idx].title = $scope.model.value[idx].caption; - $scope.model.value[idx].edit = false; - }; - - $scope.delete = function (idx) { - $scope.model.value.splice(idx, 1); - }; - - $scope.add = function ($event) { - if (!angular.isArray($scope.model.value)) { - $scope.model.value = []; - } - - if ($scope.newCaption == "") { - $scope.hasError = true; - } else { - if ($scope.addExternal) { - var newExtLink = new function() { - this.caption = $scope.newCaption; - this.link = $scope.newLink; - this.newWindow = $scope.newNewWindow; - this.edit = false; - this.isInternal = false; - this.type = "external"; - this.title = $scope.newCaption; - }; - $scope.model.value.push(newExtLink); - } else { - var newIntLink = new function() { - this.caption = $scope.newCaption; - this.link = $scope.newInternal; - this.newWindow = $scope.newNewWindow; - this.internal = $scope.newInternal; - this.edit = false; - this.isInternal = true; - this.internalName = $scope.newInternalName; - this.internalIcon = $scope.newInternalIcon; - this.type = "internal"; - this.title = $scope.newCaption; - }; - $scope.model.value.push(newIntLink); - } - $scope.newCaption = ''; - $scope.newLink = 'http://'; - $scope.newNewWindow = false; - $scope.newInternal = null; - $scope.newInternalName = ''; - $scope.newInternalIcon = null; - } - $event.preventDefault(); - }; - - $scope.switch = function ($event) { - $scope.addExternal = !$scope.addExternal; - $event.preventDefault(); - }; - - $scope.switchLinkType = function ($event, link) { - link.isInternal = !link.isInternal; - link.type = link.isInternal ? "internal" : "external"; - if (!link.isInternal) - link.link = $scope.newLink; - $event.preventDefault(); - }; - - $scope.move = function (index, direction) { - var temp = $scope.model.value[index]; - $scope.model.value[index] = $scope.model.value[index + direction]; - $scope.model.value[index + direction] = temp; - }; - - //helper for determining if a user can add items - $scope.canAdd = function () { - return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); - } - - //helper that returns if an item can be sorted - $scope.canSort = function () { - return countVisible() > 1; - } - - $scope.sortableOptions = { - axis: 'y', - handle: '.handle', - cursor: 'move', - cancel: '.no-drag', - containment: 'parent', - placeholder: 'sortable-placeholder', - forcePlaceholderSize: true, - helper: function (e, ui) { - // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ - ui.children().each(function () { - $(this).width($(this).width()); - }); - return ui; - }, - items: '> tr:not(.unsortable)', - tolerance: 'pointer', - update: function (e, ui) { - // Get the new and old index for the moved element (using the URL as the identifier) - var newIndex = ui.item.index(); - var movedLinkUrl = ui.item.attr('data-link'); - var originalIndex = getElementIndexByUrl(movedLinkUrl); - - // Move the element in the model - var movedElement = $scope.model.value[originalIndex]; - $scope.model.value.splice(originalIndex, 1); - $scope.model.value.splice(newIndex, 0, movedElement); - }, - start: function (e, ui) { - //ui.placeholder.html(""); - - // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size - var cellCount = 0; - $('td, th', ui.helper).each(function () { - // For each td or th try and get it's colspan attribute, and add that or 1 to the total - var colspan = 1; - var colspanAttr = $(this).attr('colspan'); - if (colspanAttr > 1) { - colspan = colspanAttr; - } - cellCount += colspan; - }); - - // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr - ui.placeholder.html('').height(ui.item.height()); - } - }; - - //helper to count what is visible - function countVisible() { - return $scope.model.value.length; - } - - function isNumeric(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - } - - function getElementIndexByUrl(url) { - for (var i = 0; i < $scope.model.value.length; i++) { - if ($scope.model.value[i].link == url) { - return i; - } - } - - return -1; - } - - function select(data) { - if ($scope.currentEditLink != null) { - $scope.currentEditLink.internal = $scope.model.config.idType === "udi" ? data.udi : data.id; - $scope.currentEditLink.internalName = data.name; - $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); - $scope.currentEditLink.link = $scope.model.config.idType === "udi" ? data.udi : data.id; - } else { - $scope.newInternal = $scope.model.config.idType === "udi" ? data.udi : data.id; - $scope.newInternalName = data.name; - $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); - } - } - }); +angular.module("umbraco") + .controller("Umbraco.PropertyEditors.RelatedLinksController", + function ($rootScope, $scope, dialogService, iconHelper) { + + if (!$scope.model.value) { + $scope.model.value = []; + } + + $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE; + + $scope.newCaption = ''; + $scope.newLink = 'http://'; + $scope.newNewWindow = false; + $scope.newInternal = null; + $scope.newInternalName = ''; + $scope.newInternalIcon = null; + $scope.addExternal = true; + $scope.currentEditLink = null; + $scope.hasError = false; + + $scope.internal = function($event) { + $scope.currentEditLink = null; + + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = "contentpicker"; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; + + $scope.contentPickerOverlay.submit = function(model) { + + select(model.selection[0]); + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $event.preventDefault(); + }; + + $scope.selectInternal = function ($event, link) { + $scope.currentEditLink = link; + + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = "contentpicker"; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; + + $scope.contentPickerOverlay.submit = function(model) { + + select(model.selection[0]); + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $event.preventDefault(); + + }; + + $scope.edit = function (idx) { + for (var i = 0; i < $scope.model.value.length; i++) { + $scope.model.value[i].edit = false; + } + $scope.model.value[idx].edit = true; + }; + + $scope.saveEdit = function (idx) { + $scope.model.value[idx].title = $scope.model.value[idx].caption; + $scope.model.value[idx].edit = false; + }; + + $scope.delete = function (idx) { + $scope.model.value.splice(idx, 1); + }; + + $scope.add = function ($event) { + if (!angular.isArray($scope.model.value)) { + $scope.model.value = []; + } + + if ($scope.newCaption == "") { + $scope.hasError = true; + } else { + if ($scope.addExternal) { + var newExtLink = new function() { + this.caption = $scope.newCaption; + this.link = $scope.newLink; + this.newWindow = $scope.newNewWindow; + this.edit = false; + this.isInternal = false; + this.type = "external"; + this.title = $scope.newCaption; + }; + $scope.model.value.push(newExtLink); + } else { + var newIntLink = new function() { + this.caption = $scope.newCaption; + this.link = $scope.newInternal; + this.newWindow = $scope.newNewWindow; + this.internal = $scope.newInternal; + this.edit = false; + this.isInternal = true; + this.internalName = $scope.newInternalName; + this.internalIcon = $scope.newInternalIcon; + this.type = "internal"; + this.title = $scope.newCaption; + }; + $scope.model.value.push(newIntLink); + } + $scope.newCaption = ''; + $scope.newLink = 'http://'; + $scope.newNewWindow = false; + $scope.newInternal = null; + $scope.newInternalName = ''; + $scope.newInternalIcon = null; + } + $event.preventDefault(); + }; + + $scope.switch = function ($event) { + $scope.addExternal = !$scope.addExternal; + $event.preventDefault(); + }; + + $scope.switchLinkType = function ($event, link) { + link.isInternal = !link.isInternal; + link.type = link.isInternal ? "internal" : "external"; + if (!link.isInternal) + link.link = $scope.newLink; + $event.preventDefault(); + }; + + $scope.move = function (index, direction) { + var temp = $scope.model.value[index]; + $scope.model.value[index] = $scope.model.value[index + direction]; + $scope.model.value[index + direction] = temp; + }; + + //helper for determining if a user can add items + $scope.canAdd = function () { + return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); + } + + //helper that returns if an item can be sorted + $scope.canSort = function () { + return countVisible() > 1; + } + + $scope.sortableOptions = { + axis: 'y', + handle: '.handle', + cursor: 'move', + cancel: '.no-drag', + containment: 'parent', + placeholder: 'sortable-placeholder', + forcePlaceholderSize: true, + helper: function (e, ui) { + // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ + ui.children().each(function () { + $(this).width($(this).width()); + }); + return ui; + }, + items: '> tr:not(.unsortable)', + tolerance: 'pointer', + update: function (e, ui) { + // Get the new and old index for the moved element (using the URL as the identifier) + var newIndex = ui.item.index(); + var movedLinkUrl = ui.item.attr('data-link'); + var originalIndex = getElementIndexByUrl(movedLinkUrl); + + // Move the element in the model + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + }, + start: function (e, ui) { + //ui.placeholder.html(""); + + // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size + var cellCount = 0; + $('td, th', ui.helper).each(function () { + // For each td or th try and get it's colspan attribute, and add that or 1 to the total + var colspan = 1; + var colspanAttr = $(this).attr('colspan'); + if (colspanAttr > 1) { + colspan = colspanAttr; + } + cellCount += colspan; + }); + + // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr + ui.placeholder.html('').height(ui.item.height()); + } + }; + + //helper to count what is visible + function countVisible() { + return $scope.model.value.length; + } + + function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + + function getElementIndexByUrl(url) { + for (var i = 0; i < $scope.model.value.length; i++) { + if ($scope.model.value[i].link == url) { + return i; + } + } + + return -1; + } + + function select(data) { + if ($scope.currentEditLink != null) { + $scope.currentEditLink.internal = $scope.model.config.idType === "udi" ? data.udi : data.id; + $scope.currentEditLink.internalName = data.name; + $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); + $scope.currentEditLink.link = $scope.model.config.idType === "udi" ? data.udi : data.id; + } else { + $scope.newInternal = $scope.model.config.idType === "udi" ? data.udi : data.id; + $scope.newInternalName = data.name; + $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); + } + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index f8dea2ee8b..cadbc5f57a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -1,403 +1,402 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.RTEController", - function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { - - $scope.isLoading = true; - - //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias - // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because - // we have this mini content editor panel that can be launched with MNTP. - var d = new Date(); - var n = d.getTime(); - $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte"; - - function syncContent(editor){ - editor.save(); - angularHelper.safeApply($scope, function () { - $scope.model.value = editor.getContent(); - }); - - //make the form dirty manually so that the track changes works, setting our model doesn't trigger - // the angular bits because tinymce replaces the textarea. - angularHelper.getCurrentForm($scope).$setDirty(); - } - - tinyMceService.configuration().then(function (tinyMceConfig) { - - //config value from general tinymce.config file - var validElements = tinyMceConfig.validElements; - - //These are absolutely required in order for the macros to render inline - //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]"; - - var invalidElements = tinyMceConfig.inValidElements; - var plugins = _.map(tinyMceConfig.plugins, function (plugin) { - if (plugin.useOnFrontend) { - return plugin.name; - } - }).join(" "); - - var editorConfig = $scope.model.config.editor; - if (!editorConfig || angular.isString(editorConfig)) { - editorConfig = tinyMceService.defaultPrevalues(); - } - - //config value on the data type - var toolbar = editorConfig.toolbar.join(" | "); - var stylesheets = []; - var styleFormats = []; - var await = []; - if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) { - editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; - } - - //queue file loading - if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded - await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope)); - } - - //queue rules loading - angular.forEach(editorConfig.stylesheets, function (val, key) { - stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime()); - await.push(stylesheetResource.getRulesByName(val).then(function (rules) { - angular.forEach(rules, function (rule) { - var r = {}; - r.title = rule.name; - if (rule.selector[0] == ".") { - r.inline = "span"; - r.classes = rule.selector.substring(1); - } - else if (rule.selector[0] == "#") { - r.inline = "span"; - r.attributes = { id: rule.selector.substring(1) }; - } - else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { - var split = rule.selector.split("."); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); - } - else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { - var split = rule.selector.split("#"); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); - } - else { - r.block = rule.selector; - } - - styleFormats.push(r); - }); - })); - }); - - - //stores a reference to the editor - var tinyMceEditor = null; - - // these languages are available for localization - var availableLanguages = [ - 'da', - 'de', - 'en', - 'en_us', - 'fi', - 'fr', - 'he', - 'it', - 'ja', - 'nl', - 'no', - 'pl', - 'pt', - 'ru', - 'sv', - 'zh' - ]; - - //define fallback language - var language = 'en_us'; - //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc. - //wheras tinymce is in the format of ru, de, en, en_us, etc. - var localeId = $locale.id.replace('-', '_'); - //try matching the language using full locale format - var languageMatch = _.find(availableLanguages, function(o) { return o === localeId; }); - //if no matches, try matching using only the language - if (languageMatch === undefined) { - var localeParts = localeId.split('_'); - languageMatch = _.find(availableLanguages, function(o) { return o === localeParts[0]; }); - } - //if a match was found - set the language - if (languageMatch !== undefined) { - language = languageMatch; - } - - //wait for queue to end - $q.all(await).then(function () { - - //create a baseline Config to exten upon - var baseLineConfigObj = { - mode: "exact", - skin: "umbraco", - plugins: plugins, - valid_elements: validElements, - invalid_elements: invalidElements, - extended_valid_elements: extendedValidElements, - menubar: false, - statusbar: false, - relative_urls: false, - height: editorConfig.dimensions.height, - width: editorConfig.dimensions.width, - maxImageSize: editorConfig.maxImageSize, - toolbar: toolbar, - content_css: stylesheets, - style_formats: styleFormats, - language: language, - //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix - cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster - }; - - if (tinyMceConfig.customConfig) { - - //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to - // convert it to json instead of having it as a string since this is what tinymce requires - for (var i in tinyMceConfig.customConfig) { - var val = tinyMceConfig.customConfig[i]; - if (val) { - val = val.toString().trim(); - if (val.detectIsJson()) { - try { - tinyMceConfig.customConfig[i] = JSON.parse(val); - //now we need to check if this custom config key is defined in our baseline, if it is we don't want to - //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise - //if it's an object it will overwrite the baseline - if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { - //concat it and below this concat'd array will overwrite the baseline in angular.extend - tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); - } - } - catch (e) { - //cannot parse, we'll just leave it - } - } - if (val === "true") { - tinyMceConfig.customConfig[i] = true; - } - if (val === "false") { - tinyMceConfig.customConfig[i] = false; - } - } - } - - angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); - } - - //set all the things that user configs should not be able to override - baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace! - baseLineConfigObj.setup = function (editor) { - - //set the reference - tinyMceEditor = editor; - - //enable browser based spell checking - editor.on('init', function (e) { - editor.getBody().setAttribute('spellcheck', true); - }); - - //We need to listen on multiple things here because of the nature of tinymce, it doesn't - //fire events when you think! - //The change event doesn't fire when content changes, only when cursor points are changed and undo points - //are created. the blur event doesn't fire if you insert content into the editor with a button and then - //press save. - //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can - //listen to both change and blur and also on our own 'saving' event. I think this will be best because a - //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked - //save before the timeout elapsed. - - //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce - // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers - // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed. - // see: http://issues.umbraco.org/issue/U4-4485 - //var alreadyDirty = false; - //editor.on('change', function (e) { - // angularHelper.safeApply($scope, function () { - // $scope.model.value = editor.getContent(); - - // if (!alreadyDirty) { - // //make the form dirty manually so that the track changes works, setting our model doesn't trigger - // // the angular bits because tinymce replaces the textarea. - // var currForm = angularHelper.getCurrentForm($scope); - // currForm.$setDirty(); - // alreadyDirty = true; - // } - - // }); - //}); - - //when we leave the editor (maybe) - editor.on('blur', function (e) { - editor.save(); - angularHelper.safeApply($scope, function () { - $scope.model.value = editor.getContent(); - }); - }); - - //when buttons modify content - editor.on('ExecCommand', function (e) { - syncContent(editor); - }); - - // Update model on keypress - editor.on('KeyUp', function (e) { - syncContent(editor); - }); - - // Update model on change, i.e. copy/pasted text, plugins altering content - editor.on('SetContent', function (e) { - if (!e.initial) { - syncContent(editor); - } - }); - - - editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max"; - var srcAttr = $(e.target).attr("src"); - var path = srcAttr.split("?")[0]; - $(e.target).attr("data-mce-src", path + qs); - - syncContent(editor); - }); - - tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { - $scope.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - $scope.linkPickerOverlay.show = false; - $scope.linkPickerOverlay = null; - } - }; - }); - - //Create the insert media plugin - tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ - var ignoreUserStartNodes = false; - var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - var startNodeIsVirtual = userData.startMediaIds.length !== 1; - - if ($scope.model.config.ignoreUserStartNodes === "1") { - ignoreUserStartNodes = true; - startNodeId = -1; - startNodeIsVirtual = true; - } - - $scope.mediaPickerOverlay = { - currentTarget: currentTarget, - onlyImages: true, - showDetails: true, - disableFolderSelect: true, - startNodeId: startNodeId, - startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, - view: "mediapicker", - show: true, - submit: function(model) { - tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; - - }); - - //Create the embedded plugin - tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() { - - $scope.embedOverlay = { - view: "embed", - show: true, - submit: function(model) { - tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); - $scope.embedOverlay.show = false; - $scope.embedOverlay = null; - } - }; - - }); - - - //Create the insert macro plugin - tinyMceService.createInsertMacro(editor, $scope, function(dialogData) { - - $scope.macroPickerOverlay = { - view: "macropicker", - dialogData: dialogData, - show: true, - submit: function(model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - tinyMceService.insertMacroInEditor(editor, macroObject, $scope); - $scope.macroPickerOverlay.show = false; - $scope.macroPickerOverlay = null; - } - }; - - }); - }; - - /** Loads in the editor */ - function loadTinyMce() { - - //we need to add a timeout here, to force a redraw so TinyMCE can find - //the elements needed - $timeout(function () { - tinymce.DOM.events.domLoaded = true; - tinymce.init(baseLineConfigObj); - - $scope.isLoading = false; - - }, 200, false); - } - - - - - loadTinyMce(); - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server; - //uses an empty string in the editor when the value is null - tinyMceEditor.setContent(newVal || "", { format: 'raw' }); - //we need to manually fire this event since it is only ever fired based on loading from the DOM, this - // is required for our plugins listening to this event to execute - tinyMceEditor.fire('LoadContent', null); - }; - - //listen for formSubmitting event (the result is callback used to remove the event subscription) - var unsubscribe = $scope.$on("formSubmitting", function () { - //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer - // we do parse it out on the server side but would be nice to do that on the client side before as well. - if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) { - $scope.model.value = tinyMceEditor.getContent(); - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom - // element might still be there even after the modal has been hidden. - $scope.$on('$destroy', function () { - unsubscribe(); - if (tinyMceEditor !== undefined && tinyMceEditor != null) { - tinyMceEditor.destroy(); - } - }); - }); - }); - - }); +angular.module("umbraco") + .controller("Umbraco.PropertyEditors.RTEController", + function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { + + $scope.isLoading = true; + + //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias + // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because + // we have this mini content editor panel that can be launched with MNTP. + var d = new Date(); + var n = d.getTime(); + $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte"; + + function syncContent(editor){ + editor.save(); + angularHelper.safeApply($scope, function () { + $scope.model.value = editor.getContent(); + }); + + //make the form dirty manually so that the track changes works, setting our model doesn't trigger + // the angular bits because tinymce replaces the textarea. + angularHelper.getCurrentForm($scope).$setDirty(); + } + + tinyMceService.configuration().then(function (tinyMceConfig) { + + //config value from general tinymce.config file + var validElements = tinyMceConfig.validElements; + + //These are absolutely required in order for the macros to render inline + //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce + var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]"; + + var invalidElements = tinyMceConfig.inValidElements; + var plugins = _.map(tinyMceConfig.plugins, function (plugin) { + if (plugin.useOnFrontend) { + return plugin.name; + } + }).join(" "); + + var editorConfig = $scope.model.config.editor; + if (!editorConfig || angular.isString(editorConfig)) { + editorConfig = tinyMceService.defaultPrevalues(); + } + + //config value on the data type + var toolbar = editorConfig.toolbar.join(" | "); + var stylesheets = []; + var styleFormats = []; + var await = []; + if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) { + editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; + } + + //queue file loading + if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded + await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope)); + } + + //queue rules loading + angular.forEach(editorConfig.stylesheets, function (val, key) { + stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime()); + await.push(stylesheetResource.getRulesByName(val).then(function (rules) { + angular.forEach(rules, function (rule) { + var r = {}; + r.title = rule.name; + if (rule.selector[0] == ".") { + r.inline = "span"; + r.classes = rule.selector.substring(1); + } + else if (rule.selector[0] == "#") { + r.inline = "span"; + r.attributes = { id: rule.selector.substring(1) }; + } + else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { + var split = rule.selector.split("."); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); + } + else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { + var split = rule.selector.split("#"); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); + } + else { + r.block = rule.selector; + } + + styleFormats.push(r); + }); + })); + }); + + + //stores a reference to the editor + var tinyMceEditor = null; + + // these languages are available for localization + var availableLanguages = [ + 'da', + 'de', + 'en', + 'en_us', + 'fi', + 'fr', + 'he', + 'it', + 'ja', + 'nl', + 'no', + 'pl', + 'pt', + 'ru', + 'sv', + 'zh' + ]; + + //define fallback language + var language = 'en_us'; + //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc. + //wheras tinymce is in the format of ru, de, en, en_us, etc. + var localeId = $locale.id.replace('-', '_'); + //try matching the language using full locale format + var languageMatch = _.find(availableLanguages, function(o) { return o === localeId; }); + //if no matches, try matching using only the language + if (languageMatch === undefined) { + var localeParts = localeId.split('_'); + languageMatch = _.find(availableLanguages, function(o) { return o === localeParts[0]; }); + } + //if a match was found - set the language + if (languageMatch !== undefined) { + language = languageMatch; + } + + //wait for queue to end + $q.all(await).then(function () { + + //create a baseline Config to exten upon + var baseLineConfigObj = { + mode: "exact", + skin: "umbraco", + plugins: plugins, + valid_elements: validElements, + invalid_elements: invalidElements, + extended_valid_elements: extendedValidElements, + menubar: false, + statusbar: false, + relative_urls: false, + height: editorConfig.dimensions.height, + width: editorConfig.dimensions.width, + maxImageSize: editorConfig.maxImageSize, + toolbar: toolbar, + content_css: stylesheets, + style_formats: styleFormats, + language: language, + //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix + cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster + }; + + if (tinyMceConfig.customConfig) { + + //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to + // convert it to json instead of having it as a string since this is what tinymce requires + for (var i in tinyMceConfig.customConfig) { + var val = tinyMceConfig.customConfig[i]; + if (val) { + val = val.toString().trim(); + if (val.detectIsJson()) { + try { + tinyMceConfig.customConfig[i] = JSON.parse(val); + //now we need to check if this custom config key is defined in our baseline, if it is we don't want to + //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise + //if it's an object it will overwrite the baseline + if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { + //concat it and below this concat'd array will overwrite the baseline in angular.extend + tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); + } + } + catch (e) { + //cannot parse, we'll just leave it + } + } + if (val === "true") { + tinyMceConfig.customConfig[i] = true; + } + if (val === "false") { + tinyMceConfig.customConfig[i] = false; + } + } + } + + angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); + } + + //set all the things that user configs should not be able to override + baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace! + baseLineConfigObj.setup = function (editor) { + + //set the reference + tinyMceEditor = editor; + + //enable browser based spell checking + editor.on('init', function (e) { + editor.getBody().setAttribute('spellcheck', true); + }); + + //We need to listen on multiple things here because of the nature of tinymce, it doesn't + //fire events when you think! + //The change event doesn't fire when content changes, only when cursor points are changed and undo points + //are created. the blur event doesn't fire if you insert content into the editor with a button and then + //press save. + //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can + //listen to both change and blur and also on our own 'saving' event. I think this will be best because a + //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked + //save before the timeout elapsed. + + //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce + // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers + // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed. + // see: http://issues.umbraco.org/issue/U4-4485 + //var alreadyDirty = false; + //editor.on('change', function (e) { + // angularHelper.safeApply($scope, function () { + // $scope.model.value = editor.getContent(); + + // if (!alreadyDirty) { + // //make the form dirty manually so that the track changes works, setting our model doesn't trigger + // // the angular bits because tinymce replaces the textarea. + // var currForm = angularHelper.getCurrentForm($scope); + // currForm.$setDirty(); + // alreadyDirty = true; + // } + + // }); + //}); + + //when we leave the editor (maybe) + editor.on('blur', function (e) { + editor.save(); + angularHelper.safeApply($scope, function () { + $scope.model.value = editor.getContent(); + }); + }); + + //when buttons modify content + editor.on('ExecCommand', function (e) { + syncContent(editor); + }); + + // Update model on keypress + editor.on('KeyUp', function (e) { + syncContent(editor); + }); + + // Update model on change, i.e. copy/pasted text, plugins altering content + editor.on('SetContent', function (e) { + if (!e.initial) { + syncContent(editor); + } + }); + + + editor.on('ObjectResized', function (e) { + var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max"; + var srcAttr = $(e.target).attr("src"); + var path = srcAttr.split("?")[0]; + $(e.target).attr("data-mce-src", path + qs); + + syncContent(editor); + }); + + tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { + $scope.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + $scope.linkPickerOverlay.show = false; + $scope.linkPickerOverlay = null; + } + }; + }); + + //Create the insert media plugin + tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + startNodeId = -1; + startNodeIsVirtual = true; + } + + $scope.mediaPickerOverlay = { + currentTarget: currentTarget, + onlyImages: true, + showDetails: true, + disableFolderSelect: true, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + dataTypeId: $scope.model.dataTypeId, + view: "mediapicker", + show: true, + submit: function(model) { + tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + + }); + + //Create the embedded plugin + tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() { + + $scope.embedOverlay = { + view: "embed", + show: true, + submit: function(model) { + tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); + $scope.embedOverlay.show = false; + $scope.embedOverlay = null; + } + }; + + }); + + + //Create the insert macro plugin + tinyMceService.createInsertMacro(editor, $scope, function(dialogData) { + + $scope.macroPickerOverlay = { + view: "macropicker", + dialogData: dialogData, + show: true, + submit: function(model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + tinyMceService.insertMacroInEditor(editor, macroObject, $scope); + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + } + }; + + }); + }; + + /** Loads in the editor */ + function loadTinyMce() { + + //we need to add a timeout here, to force a redraw so TinyMCE can find + //the elements needed + $timeout(function () { + tinymce.DOM.events.domLoaded = true; + tinymce.init(baseLineConfigObj); + + $scope.isLoading = false; + + }, 200, false); + } + + + + + loadTinyMce(); + + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server; + //uses an empty string in the editor when the value is null + tinyMceEditor.setContent(newVal || "", { format: 'raw' }); + //we need to manually fire this event since it is only ever fired based on loading from the DOM, this + // is required for our plugins listening to this event to execute + tinyMceEditor.fire('LoadContent', null); + }; + + //listen for formSubmitting event (the result is callback used to remove the event subscription) + var unsubscribe = $scope.$on("formSubmitting", function () { + //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer + // we do parse it out on the server side but would be nice to do that on the client side before as well. + if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) { + $scope.model.value = tinyMceEditor.getContent(); + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom + // element might still be there even after the modal has been hidden. + $scope.$on('$destroy', function () { + unsubscribe(); + if (tinyMceEditor !== undefined && tinyMceEditor != null) { + tinyMceEditor.destroy(); + } + }); + }); + }); + + }); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b8d9c8bcb6..c909c1eb84 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1,1227 +1,1227 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.ModelBinding; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Binders; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Events; -using Constants = Umbraco.Core.Constants; -using umbraco.cms.businesslogic; -using System.Collections; -using umbraco; - -namespace Umbraco.Web.Editors -{ - /// - /// The API controller used for editing content - /// - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the content application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Constants.Applications.Content)] - [ContentControllerConfiguration] - public class ContentController : ContentControllerBase - { - /// - /// Constructor - /// - public ContentController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public ContentController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - /// - /// Configures this controller with a custom action selector - /// - private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) - )); - } - } - - /// - /// Return content for the specified ids - /// - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundContent = Services.ContentService.GetByIds(ids); - return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext)); - } - - /// - /// Updates the permissions for a content item for a particular user group - /// - /// - /// - /// - /// Permission check is done for letter 'R' which is for which the user must have access to to update - /// - [EnsureUserPermissionForContent("saveModel.ContentId", 'R')] - public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) - { - if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //TODO: Should non-admins be alowed to set granular permissions? - - var content = Services.ContentService.GetById(saveModel.ContentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //current permissions explicitly assigned to this content item - var contentPermissions = Services.ContentService.GetPermissionsForEntity(content) - .ToDictionary(x => x.UserGroupId, x => x); - - var allUserGroups = Services.UserService.GetAllUserGroups().ToArray(); - - //loop through each user group - foreach (var userGroup in allUserGroups) - { - //check if there's a permission set posted up for this user group - IEnumerable groupPermissions; - if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) - { - //create a string collection of the assigned letters - var groupPermissionCodes = groupPermissions.ToArray(); - - //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions - //for this group/node which will go back to the defaults - if (groupPermissionCodes.Length == 0) - { - Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); - } - //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored - else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes)) - { - //only remove them if they are actually currently assigned - if (contentPermissions.ContainsKey(userGroup.Id)) - { - //remove these permissions from this node for this group since the ones being assigned are the same as the defaults - Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); - } - } - //if they are different we need to update, otherwise there's nothing to update - else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) - { - - Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); - } - } - } - - return GetDetailedPermissions(content, allUserGroups); - } - - /// - /// Returns the user group permissions for user groups assigned to this node - /// - /// - /// - /// - /// Permission check is done for letter 'R' which is for which the user must have access to to view - /// - [EnsureUserPermissionForContent("contentId", 'R')] - public IEnumerable GetDetailedPermissions(int contentId) - { - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var content = Services.ContentService.GetById(contentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //TODO: Should non-admins be able to see detailed permissions? - - var allUserGroups = Services.UserService.GetAllUserGroups(); - - return GetDetailedPermissions(content, allUserGroups); - } - - private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups) - { - //get all user groups and map their default permissions to the AssignedUserGroupPermissions model. - //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults. - - var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray(); - - var defaultPermissionsAsDictionary = defaultPermissionsByGroup - .ToDictionary(x => Convert.ToInt32(x.Id), x => x); - - //get the actual assigned permissions - var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray(); - - //iterate over assigned and update the defaults with the real values - foreach (var assignedGroupPermission in assignedPermissionsByGroup) - { - var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId]; - - //clone the default permissions model to the assigned ones - defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions); - - //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions - //and we'll re-check it if it's one of the explicitly assigned ones - foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value)) - { - permission.Checked = false; - permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); - } - - } - - return defaultPermissionsByGroup; - } - - /// - /// Returns an item to be used to display the recycle bin for content - /// - /// - public ContentItemDisplay GetRecycleBin() - { - var display = new ContentItemDisplay - { - Id = Constants.System.RecycleBinContent, - Alias = "recycleBin", - ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinContent - }; - - TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); - - return display; - } - - public ContentItemDisplay GetBlueprintById(int id) - { - var foundContent = Services.ContentService.GetBlueprintById(id); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - - SetupBlueprint(content, foundContent); - - return content; - } - - private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent) - { - content.AllowPreview = false; - - //set a custom path since the tree that renders this has the content type id as the parent - content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id); - - content.AllowedActions = new[] { "A" }; - content.IsBlueprint = true; - - var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" }; - var propsTab = content.Tabs.Last(); - propsTab.Properties = propsTab.Properties - .Where(p => excludeProps.Contains(p.Alias) == false); - } - - /// - /// Gets the content json for the content id - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id, [FromUri]bool ignoreUserStartNodes = false) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - return content; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(Guid id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - return content; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetById(guidUdi.Guid); - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetWithTreeDefinition(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - return content; - } - - /// - /// Gets an empty content item for the - /// - /// - /// - /// - /// If this is a container type, we'll remove the umbContainerView tab for a new item since - /// it cannot actually list children if it doesn't exist yet. - /// - [OutgoingEditorModelEvent] - public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) - { - var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); - // translate the content type name if applicable - mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName); - // if your user type doesn't have access to the Settings section it would not get this property mapped - if(mapped.DocumentType != null) - mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - [OutgoingEditorModelEvent] - public ContentItemDisplay GetEmpty(int blueprintId, int parentId) - { - var blueprint = Services.ContentService.GetBlueprintById(blueprintId); - if (blueprint == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - blueprint.Id = 0; - blueprint.Name = string.Empty; - blueprint.ParentId = parentId; - - var mapped = Mapper.Map(blueprint); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(int id) - { - var url = Umbraco.NiceUrl(id); - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(Guid id) - { - var url = Umbraco.UrlProvider.GetUrl(id); - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetNiceUrl(guidUdi.Guid); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// Gets the children for the content id passed in - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren( - int id, - int pageNumber = 0, //TODO: This should be '1' as it's not the index - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Gets the children for the content id passed in - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren( - int id, - string includeProperties, - int pageNumber = 0, //TODO: This should be '1' as it's not the index - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - long totalChildren; - IContent[] children; - if (pageNumber > 0 && pageSize > 0) - { - children = Services.ContentService - .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemField, filter).ToArray(); - } - else - { - children = Services.ContentService.GetChildren(id).ToArray(); - totalChildren = children.Length; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children.Select(content => - Mapper.Map>(content, - opts => - { - // if there's a list of property aliases to map - we will make sure to store this in the mapping context. - if (String.IsNullOrWhiteSpace(includeProperties) == false) - { - opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries); - } - })); - - return pagedResult; - } - - [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] - public bool GetHasPermission(string permissionToCheck, int nodeId) - { - return HasPermission(permissionToCheck, nodeId); - } - - /// - /// Returns permissions for all nodes passed in for the current user - /// TODO: This should be moved to the CurrentUserController? - /// - /// - /// - [HttpPost] - public Dictionary GetPermissions(int[] nodeIds) - { - var permissions = Services.UserService - .GetPermissions(Security.CurrentUser, nodeIds); - - var permissionsDictionary = new Dictionary(); - foreach (var nodeId in nodeIds) - { - var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray(); - permissionsDictionary.Add(nodeId, aggregatePerms); - } - - return permissionsDictionary; - } - - /// - /// Checks a nodes permission for the current user - /// TODO: This should be moved to the CurrentUserController? - /// - /// - /// - /// - [HttpGet] - public bool HasPermission(string permissionToCheck, int nodeId) - { - var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions(); - if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) - { - return true; - } - - return false; - } - - /// - /// Creates a blueprint from a content item - /// - /// The content id to copy - /// The name of the blueprint - /// - [HttpPost] - public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name) - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); - - var content = Services.ContentService.GetById(contentId); - if (content == null) - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - EnsureUniqueName(name, content, "name"); - - var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id); - - Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id); - - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddSuccessNotification( - Services.TextService.Localize("blueprints/createdBlueprintHeading"), - Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name }) - ); - - return notificationModel; - } - - private void EnsureUniqueName(string name, IContent content, string modelName) - { - var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId); - if (existing.Any(x => x.Name == name && x.Id != content.Id)) - { - ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage")); - throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); - } - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [ContentPostValidate] - public ContentItemDisplay PostSaveBlueprint( - [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) - { - var contentItemDisplay = PostSaveInternal(contentItem, - content => - { - EnsureUniqueName(content.Name, content, "Name"); - - Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); - //we need to reuse the underlying logic so return the result that it wants - return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages())); - }); - SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); - - return contentItemDisplay; - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [ContentPostValidate] - [OutgoingEditorModelEvent] - public ContentItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) - { - return PostSaveInternal(contentItem, - content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id)); - } - - private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod) - { - //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). - if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) - { - foreach (var file in contentItem.UploadedFiles) - { - file.FileName = Path.GetFileName(file.FileName); - } - } - - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - MapPropertyValues(contentItem); - - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw a validation message - var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - - } - - //if the model state is not valid we cannot publish so change it to save - switch (contentItem.Action) - { - case ContentSaveAction.Publish: - contentItem.Action = ContentSaveAction.Save; - break; - case ContentSaveAction.PublishNew: - contentItem.Action = ContentSaveAction.SaveNew; - break; - } - } - - //initialize this to successful - var publishStatus = Attempt.Succeed(); - var wasCancelled = false; - - if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) - { - //save the item - var saveResult = saveMethod(contentItem.PersistedContent); - - wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; - } - else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) - { - var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = sendResult == false; - } - else - { - //publish the item and check if it worked, if not we will show a diff msg below - publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; - } - - //return the updated model - var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); - - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - contentItem.ReleaseDate.HasValue - ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() }) - : Services.TextService.Localize("speechBubbles/editContentSavedText") - ); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.SendPublish: - case ContentSaveAction.SendPublishNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: - ShowMessageForPublishStatus(publishStatus.Result, display, contentItem.ExpireDate); - break; - } - - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (wasCancelled && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } - - display.PersistedContent = contentItem.PersistedContent; - - return display; - } - - /// - /// Publishes a document with a given ID - /// - /// - /// - /// - /// The CanAccessContentAuthorize attribute will deny access to this method if the current user - /// does not have Publish access to this node. - /// - /// - [EnsureUserPermissionForContent("id", 'U')] - public HttpResponseMessage PostPublishById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - { - return HandleContentNotFound(id, false); - } - - var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.CurrentUser.Id); - if (publishResult.Success == false) - { - var notificationModel = new SimpleNotificationModel(); - ShowMessageForPublishStatus(publishResult.Result, notificationModel, foundContent.ExpireDate); - return Request.CreateValidationErrorResponse(notificationModel); - } - - //return ok - return Request.CreateResponse(HttpStatusCode.OK); - - } - - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteBlueprint(int id) - { - var found = Services.ContentService.GetBlueprintById(id); - - if (found == null) - { - return HandleContentNotFound(id, false); - } - - Services.ContentService.DeleteBlueprint(found); - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - /// - /// The CanAccessContentAuthorize attribute will deny access to this method if the current user - /// does not have Delete access to this node. - /// - [EnsureUserPermissionForContent("id", 'D')] - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - { - return HandleContentNotFound(id, false); - } - - //if the current item is in the recycle bin - if (foundContent.IsInRecycleBin() == false) - { - var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.CurrentUser.Id); - if (moveResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - else - { - var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.CurrentUser.Id); - if (deleteResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Empties the recycle bin - /// - /// - /// - /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin - /// - [HttpDelete] - [HttpPost] - [EnsureUserPermissionForContent(Constants.System.RecycleBinContent, 'D')] - public HttpResponseMessage EmptyRecycleBin() - { - Services.ContentService.EmptyRecycleBin(Security.CurrentUser.Id); - - return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); - } - - /// - /// Change the sort order for content - /// - /// - /// - [EnsureUserPermissionForContent("sorted.ParentId", 'S')] - public HttpResponseMessage PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } - - try - { - var contentService = Services.ContentService; - - // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) - { - LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); - return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); - } - return Request.CreateResponse(HttpStatusCode.OK); - } - catch (Exception ex) - { - LogHelper.Error("Could not update content sort order", ex); - throw; - } - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForContent("move.ParentId", 'M')] - public HttpResponseMessage PostMove(MoveOrCopy move) - { - var toMove = ValidateMoveOrCopy(move); - - Services.ContentService.Move(toMove, move.ParentId, Security.CurrentUser.Id); - - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Copies a content item and places the copy as a child of a given parent Id - /// - /// - /// - [EnsureUserPermissionForContent("copy.ParentId", 'C')] - public HttpResponseMessage PostCopy(MoveOrCopy copy) - { - var toCopy = ValidateMoveOrCopy(copy); - - var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, Security.CurrentUser.Id); - - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Unpublishes a node with a given Id and returns the unpublished entity - /// - /// - /// - [EnsureUserPermissionForContent("id", 'U')] - [OutgoingEditorModelEvent] - public ContentItemDisplay PostUnPublish(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - HandleContentNotFound(id); - - var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - - if (unpublishResult == false) - { - AddCancelMessage(content); - throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); - } - else - { - content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); - return content; - } - } - - /// - /// Maps the dto property values to the persisted model - /// - /// - private void MapPropertyValues(ContentItemSave contentItem) - { - UpdateName(contentItem); - - //TODO: We need to support 'send to publish' - - contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; - contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; - //only set the template if it didn't change - var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) - || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); - if (templateChanged) - { - var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); - if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - { - //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); - LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); - } - else - { - //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template - contentItem.PersistedContent.Template = template; - } - } - - base.MapPropertyValues(contentItem); - } - - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private IContent ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var contentService = Services.ContentService; - var toMove = contentService.GetById(model.Id); - if (toMove == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - if (model.ParentId < 0) - { - //cannot move if the content item is not allowed at the root - if (toMove.ContentType.AllowedAsRoot == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); - } - } - else - { - var parent = contentService.GetById(model.ParentId); - if (parent == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //check if the item is allowed under this one - if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); - } - - // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); - } - } - - return toMove; - } - - private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display, DateTime? expireDate) - { - switch (status.StatusType) - { - case PublishStatusType.Success: - case PublishStatusType.SuccessAlreadyPublished: - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), - expireDate.HasValue - ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() }) - : Services.TextService.Localize("speechBubbles/editContentPublishedText") - ); - break; - case PublishStatusType.FailedPathNotPublished: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedByParent", - new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); - break; - case PublishStatusType.FailedCancelledByEvent: - AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); - break; - case PublishStatusType.FailedAwaitingRelease: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", - new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); - break; - case PublishStatusType.FailedHasExpired: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedExpired", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - }).Trim()); - break; - case PublishStatusType.FailedIsTrashed: - //TODO: We should add proper error messaging for this! - break; - case PublishStatusType.FailedContentInvalid: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - string.Join(",", status.InvalidProperties.Select(x => x.Alias)) - }).Trim()); - break; - default: - throw new IndexOutOfRangeException(); - } - } - - - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// The storage to add the content item to so it can be reused - /// - /// - /// - /// - /// The content to lookup, if the contentItem is not specified - /// - /// Specifies the already resolved content item to check against - /// If set to true, user and group start node permissions will be ignored. - /// - internal static bool CheckPermissions( - IDictionary storage, - IUser user, - IUserService userService, - IContentService contentService, - IEntityService entityService, - int nodeId, - char[] permissionsToCheck = null, - IContent contentItem = null, - bool ignoreUserStartNodes = false) - { - if (storage == null) throw new ArgumentNullException("storage"); - if (user == null) throw new ArgumentNullException("user"); - if (userService == null) throw new ArgumentNullException("userService"); - if (contentService == null) throw new ArgumentNullException("contentService"); - if (entityService == null) throw new ArgumentNullException("entityService"); - - if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) - { - contentItem = contentService.GetById(nodeId); - //put the content item into storage so it can be retreived - // in the controller (saves a lookup) - storage[typeof(IContent).ToString()] = contentItem; - } - - if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - if(ignoreUserStartNodes) - { - return true; - } - - var hasPathAccess = (nodeId == Constants.System.Root) - ? user.HasContentRootAccess(entityService) - : (nodeId == Constants.System.RecycleBinContent) - ? user.HasContentBinAccess(entityService) - : user.HasPathAccess(contentItem, entityService); - - if (hasPathAccess == false) - { - return false; - } - - if (permissionsToCheck == null || permissionsToCheck.Length == 0) - { - return true; - } - - //get the implicit/inherited permissions for the user for this path, - //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) - var path = contentItem != null ? contentItem.Path : nodeId.ToString(); - var permission = userService.GetPermissionsForPath(user, path); - - var allowed = true; - foreach (var p in permissionsToCheck) - { - if (permission == null - || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) - { - allowed = false; - } - } - return allowed; - } - - [EnsureUserPermissionForContent("contentId", 'F')] - public IEnumerable GetNotificationOptions(int contentId) - { - var notifications = new List(); - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - var content = Services.ContentService.GetById(contentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - var actionList = ActionsResolver.Current.Actions; - foreach (var a in actionList) - { - if (a.ShowInNotifier) - { - NotifySetting n = new NotifySetting - { - Name = ui.Text("actions", a.Alias), - Checked = (UmbracoUser.GetNotifications(content.Path).IndexOf(a.Letter) > -1), - NotifyCode = a.Letter.ToString() - }; - notifications.Add(n); - } - } - return notifications; - } - - public void PostNotificationOptions(int contentId, string notifyOptions = "") - { - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var content = Services.ContentService.GetById(contentId); - - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var node = new CMSNode(contentId); - - global::umbraco.cms.businesslogic.workflow.Notification.UpdateNotifications(UmbracoUser, node, notifyOptions ?? ""); - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Binders; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Events; +using Constants = Umbraco.Core.Constants; +using umbraco.cms.businesslogic; +using System.Collections; +using umbraco; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for editing content + /// + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the content application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorize(Constants.Applications.Content)] + [ContentControllerConfiguration] + public class ContentController : ContentControllerBase + { + /// + /// Constructor + /// + public ContentController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public ContentController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + /// + /// Configures this controller with a custom action selector + /// + private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) + )); + } + } + + /// + /// Return content for the specified ids + /// + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundContent = Services.ContentService.GetByIds(ids); + return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext)); + } + + /// + /// Updates the permissions for a content item for a particular user group + /// + /// + /// + /// + /// Permission check is done for letter 'R' which is for which the user must have access to to update + /// + [EnsureUserPermissionForContent("saveModel.ContentId", 'R')] + public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) + { + if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //TODO: Should non-admins be alowed to set granular permissions? + + var content = Services.ContentService.GetById(saveModel.ContentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //current permissions explicitly assigned to this content item + var contentPermissions = Services.ContentService.GetPermissionsForEntity(content) + .ToDictionary(x => x.UserGroupId, x => x); + + var allUserGroups = Services.UserService.GetAllUserGroups().ToArray(); + + //loop through each user group + foreach (var userGroup in allUserGroups) + { + //check if there's a permission set posted up for this user group + IEnumerable groupPermissions; + if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) + { + //create a string collection of the assigned letters + var groupPermissionCodes = groupPermissions.ToArray(); + + //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions + //for this group/node which will go back to the defaults + if (groupPermissionCodes.Length == 0) + { + Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + } + //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored + else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes)) + { + //only remove them if they are actually currently assigned + if (contentPermissions.ContainsKey(userGroup.Id)) + { + //remove these permissions from this node for this group since the ones being assigned are the same as the defaults + Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + } + } + //if they are different we need to update, otherwise there's nothing to update + else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) + { + + Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); + } + } + } + + return GetDetailedPermissions(content, allUserGroups); + } + + /// + /// Returns the user group permissions for user groups assigned to this node + /// + /// + /// + /// + /// Permission check is done for letter 'R' which is for which the user must have access to to view + /// + [EnsureUserPermissionForContent("contentId", 'R')] + public IEnumerable GetDetailedPermissions(int contentId) + { + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var content = Services.ContentService.GetById(contentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //TODO: Should non-admins be able to see detailed permissions? + + var allUserGroups = Services.UserService.GetAllUserGroups(); + + return GetDetailedPermissions(content, allUserGroups); + } + + private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups) + { + //get all user groups and map their default permissions to the AssignedUserGroupPermissions model. + //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults. + + var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray(); + + var defaultPermissionsAsDictionary = defaultPermissionsByGroup + .ToDictionary(x => Convert.ToInt32(x.Id), x => x); + + //get the actual assigned permissions + var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray(); + + //iterate over assigned and update the defaults with the real values + foreach (var assignedGroupPermission in assignedPermissionsByGroup) + { + var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId]; + + //clone the default permissions model to the assigned ones + defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions); + + //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions + //and we'll re-check it if it's one of the explicitly assigned ones + foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value)) + { + permission.Checked = false; + permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); + } + + } + + return defaultPermissionsByGroup; + } + + /// + /// Returns an item to be used to display the recycle bin for content + /// + /// + public ContentItemDisplay GetRecycleBin() + { + var display = new ContentItemDisplay + { + Id = Constants.System.RecycleBinContent, + Alias = "recycleBin", + ParentId = -1, + Name = Services.TextService.Localize("general/recycleBin"), + ContentTypeAlias = "recycleBin", + CreateDate = DateTime.Now, + IsContainer = true, + Path = "-1," + Constants.System.RecycleBinContent + }; + + TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); + + return display; + } + + public ContentItemDisplay GetBlueprintById(int id) + { + var foundContent = Services.ContentService.GetBlueprintById(id); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + + SetupBlueprint(content, foundContent); + + return content; + } + + private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent) + { + content.AllowPreview = false; + + //set a custom path since the tree that renders this has the content type id as the parent + content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id); + + content.AllowedActions = new[] { "A" }; + content.IsBlueprint = true; + + var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" }; + var propsTab = content.Tabs.Last(); + propsTab.Properties = propsTab.Properties + .Where(p => excludeProps.Contains(p.Alias) == false); + } + + /// + /// Gets the content json for the content id + /// + /// + /// If set used to lookup whether the user and group start node permissions should be ignored. + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(int id, [FromUri]Guid? dataTypeId = null) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + return content; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(Guid id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + return content; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetById(guidUdi.Guid); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetWithTreeDefinition(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + return content; + } + + /// + /// Gets an empty content item for the + /// + /// + /// + /// + /// If this is a container type, we'll remove the umbContainerView tab for a new item since + /// it cannot actually list children if it doesn't exist yet. + /// + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) + { + var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); + var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); + // translate the content type name if applicable + mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName); + // if your user type doesn't have access to the Settings section it would not get this property mapped + if(mapped.DocumentType != null) + mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmpty(int blueprintId, int parentId) + { + var blueprint = Services.ContentService.GetBlueprintById(blueprintId); + if (blueprint == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + blueprint.Id = 0; + blueprint.Name = string.Empty; + blueprint.ParentId = parentId; + + var mapped = Mapper.Map(blueprint); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(int id) + { + var url = Umbraco.NiceUrl(id); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(Guid id) + { + var url = Umbraco.UrlProvider.GetUrl(id); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetNiceUrl(guidUdi.Guid); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Gets the children for the content id passed in + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren( + int id, + int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Gets the children for the content id passed in + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren( + int id, + string includeProperties, + int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + long totalChildren; + IContent[] children; + if (pageNumber > 0 && pageSize > 0) + { + children = Services.ContentService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + } + else + { + children = Services.ContentService.GetChildren(id).ToArray(); + totalChildren = children.Length; + } + + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children.Select(content => + Mapper.Map>(content, + opts => + { + // if there's a list of property aliases to map - we will make sure to store this in the mapping context. + if (String.IsNullOrWhiteSpace(includeProperties) == false) + { + opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries); + } + })); + + return pagedResult; + } + + [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] + public bool GetHasPermission(string permissionToCheck, int nodeId) + { + return HasPermission(permissionToCheck, nodeId); + } + + /// + /// Returns permissions for all nodes passed in for the current user + /// TODO: This should be moved to the CurrentUserController? + /// + /// + /// + [HttpPost] + public Dictionary GetPermissions(int[] nodeIds) + { + var permissions = Services.UserService + .GetPermissions(Security.CurrentUser, nodeIds); + + var permissionsDictionary = new Dictionary(); + foreach (var nodeId in nodeIds) + { + var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray(); + permissionsDictionary.Add(nodeId, aggregatePerms); + } + + return permissionsDictionary; + } + + /// + /// Checks a nodes permission for the current user + /// TODO: This should be moved to the CurrentUserController? + /// + /// + /// + /// + [HttpGet] + public bool HasPermission(string permissionToCheck, int nodeId) + { + var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions(); + if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) + { + return true; + } + + return false; + } + + /// + /// Creates a blueprint from a content item + /// + /// The content id to copy + /// The name of the blueprint + /// + [HttpPost] + public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var content = Services.ContentService.GetById(contentId); + if (content == null) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + EnsureUniqueName(name, content, "name"); + + var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id); + + Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id); + + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddSuccessNotification( + Services.TextService.Localize("blueprints/createdBlueprintHeading"), + Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name }) + ); + + return notificationModel; + } + + private void EnsureUniqueName(string name, IContent content, string modelName) + { + var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId); + if (existing.Any(x => x.Name == name && x.Id != content.Id)) + { + ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage")); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + public ContentItemDisplay PostSaveBlueprint( + [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) + { + var contentItemDisplay = PostSaveInternal(contentItem, + content => + { + EnsureUniqueName(content.Name, content, "Name"); + + Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); + //we need to reuse the underlying logic so return the result that it wants + return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages())); + }); + SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); + + return contentItemDisplay; + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + [OutgoingEditorModelEvent] + public ContentItemDisplay PostSave( + [ModelBinder(typeof(ContentItemBinder))] + ContentItemSave contentItem) + { + return PostSaveInternal(contentItem, + content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + } + + private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod) + { + //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. + //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + //uploaded files to being *only* the actual file name (as it should be). + if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) + { + foreach (var file in contentItem.UploadedFiles) + { + file.FileName = Path.GetFileName(file.FileName); + } + } + + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid + MapPropertyValues(contentItem); + + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (ModelState.IsValid == false) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) + { + //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // add the modelstate to the outgoing object and throw a validation message + var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + + } + + //if the model state is not valid we cannot publish so change it to save + switch (contentItem.Action) + { + case ContentSaveAction.Publish: + contentItem.Action = ContentSaveAction.Save; + break; + case ContentSaveAction.PublishNew: + contentItem.Action = ContentSaveAction.SaveNew; + break; + } + } + + //initialize this to successful + var publishStatus = Attempt.Succeed(); + var wasCancelled = false; + + if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) + { + //save the item + var saveResult = saveMethod(contentItem.PersistedContent); + + wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; + } + else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) + { + var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = sendResult == false; + } + else + { + //publish the item and check if it worked, if not we will show a diff msg below + publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; + } + + //return the updated model + var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); + + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + contentItem.ReleaseDate.HasValue + ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() }) + : Services.TextService.Localize("speechBubbles/editContentSavedText") + ); + } + else + { + AddCancelMessage(display); + } + break; + case ContentSaveAction.SendPublish: + case ContentSaveAction.SendPublishNew: + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + } + else + { + AddCancelMessage(display); + } + break; + case ContentSaveAction.Publish: + case ContentSaveAction.PublishNew: + ShowMessageForPublishStatus(publishStatus.Result, display, contentItem.ExpireDate); + break; + } + + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (wasCancelled && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + + display.PersistedContent = contentItem.PersistedContent; + + return display; + } + + /// + /// Publishes a document with a given ID + /// + /// + /// + /// + /// The CanAccessContentAuthorize attribute will deny access to this method if the current user + /// does not have Publish access to this node. + /// + /// + [EnsureUserPermissionForContent("id", 'U')] + public HttpResponseMessage PostPublishById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + { + return HandleContentNotFound(id, false); + } + + var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.CurrentUser.Id); + if (publishResult.Success == false) + { + var notificationModel = new SimpleNotificationModel(); + ShowMessageForPublishStatus(publishResult.Result, notificationModel, foundContent.ExpireDate); + return Request.CreateValidationErrorResponse(notificationModel); + } + + //return ok + return Request.CreateResponse(HttpStatusCode.OK); + + } + + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteBlueprint(int id) + { + var found = Services.ContentService.GetBlueprintById(id); + + if (found == null) + { + return HandleContentNotFound(id, false); + } + + Services.ContentService.DeleteBlueprint(found); + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Moves an item to the recycle bin, if it is already there then it will permanently delete it + /// + /// + /// + /// + /// The CanAccessContentAuthorize attribute will deny access to this method if the current user + /// does not have Delete access to this node. + /// + [EnsureUserPermissionForContent("id", 'D')] + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + { + return HandleContentNotFound(id, false); + } + + //if the current item is in the recycle bin + if (foundContent.IsInRecycleBin() == false) + { + var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.CurrentUser.Id); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + else + { + var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.CurrentUser.Id); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Empties the recycle bin + /// + /// + /// + /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin + /// + [HttpDelete] + [HttpPost] + [EnsureUserPermissionForContent(Constants.System.RecycleBinContent, 'D')] + public HttpResponseMessage EmptyRecycleBin() + { + Services.ContentService.EmptyRecycleBin(Security.CurrentUser.Id); + + return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); + } + + /// + /// Change the sort order for content + /// + /// + /// + [EnsureUserPermissionForContent("sorted.ParentId", 'S')] + public HttpResponseMessage PostSort(ContentSortOrder sorted) + { + if (sorted == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //if there's nothing to sort just return ok + if (sorted.IdSortOrder.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + try + { + var contentService = Services.ContentService; + + // Save content with new sort order and update content xml in db accordingly + if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) + { + LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); + return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); + } + return Request.CreateResponse(HttpStatusCode.OK); + } + catch (Exception ex) + { + LogHelper.Error("Could not update content sort order", ex); + throw; + } + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForContent("move.ParentId", 'M')] + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = ValidateMoveOrCopy(move); + + Services.ContentService.Move(toMove, move.ParentId, Security.CurrentUser.Id); + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Copies a content item and places the copy as a child of a given parent Id + /// + /// + /// + [EnsureUserPermissionForContent("copy.ParentId", 'C')] + public HttpResponseMessage PostCopy(MoveOrCopy copy) + { + var toCopy = ValidateMoveOrCopy(copy); + + var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, Security.CurrentUser.Id); + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Unpublishes a node with a given Id and returns the unpublished entity + /// + /// + /// + [EnsureUserPermissionForContent("id", 'U')] + [OutgoingEditorModelEvent] + public ContentItemDisplay PostUnPublish(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + HandleContentNotFound(id); + + var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + + if (unpublishResult == false) + { + AddCancelMessage(content); + throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); + } + else + { + content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); + return content; + } + } + + /// + /// Maps the dto property values to the persisted model + /// + /// + private void MapPropertyValues(ContentItemSave contentItem) + { + UpdateName(contentItem); + + //TODO: We need to support 'send to publish' + + contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; + contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; + //only set the template if it didn't change + var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); + if (templateChanged) + { + var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); + if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + { + //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); + LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); + } + else + { + //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template + contentItem.PersistedContent.Template = template; + } + } + + base.MapPropertyValues(contentItem); + } + + /// + /// Ensures the item can be moved/copied to the new location + /// + /// + /// + private IContent ValidateMoveOrCopy(MoveOrCopy model) + { + if (model == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var contentService = Services.ContentService; + var toMove = contentService.GetById(model.Id); + if (toMove == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + if (model.ParentId < 0) + { + //cannot move if the content item is not allowed at the root + if (toMove.ContentType.AllowedAsRoot == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); + } + } + else + { + var parent = contentService.GetById(model.ParentId); + if (parent == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //check if the item is allowed under this one + if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() + .Any(x => x.Value == toMove.ContentType.Id) == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); + } + + // Check on paths + if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); + } + } + + return toMove; + } + + private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display, DateTime? expireDate) + { + switch (status.StatusType) + { + case PublishStatusType.Success: + case PublishStatusType.SuccessAlreadyPublished: + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + expireDate.HasValue + ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() }) + : Services.TextService.Localize("speechBubbles/editContentPublishedText") + ); + break; + case PublishStatusType.FailedPathNotPublished: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedByParent", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + break; + case PublishStatusType.FailedCancelledByEvent: + AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); + break; + case PublishStatusType.FailedAwaitingRelease: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + break; + case PublishStatusType.FailedHasExpired: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedExpired", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + }).Trim()); + break; + case PublishStatusType.FailedIsTrashed: + //TODO: We should add proper error messaging for this! + break; + case PublishStatusType.FailedContentInvalid: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedInvalid", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Join(",", status.InvalidProperties.Select(x => x.Alias)) + }).Trim()); + break; + default: + throw new IndexOutOfRangeException(); + } + } + + + /// + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node + /// + /// The storage to add the content item to so it can be reused + /// + /// + /// + /// + /// The content to lookup, if the contentItem is not specified + /// + /// Specifies the already resolved content item to check against + /// If set to true, user and group start node permissions will be ignored. + /// + internal static bool CheckPermissions( + IDictionary storage, + IUser user, + IUserService userService, + IContentService contentService, + IEntityService entityService, + int nodeId, + char[] permissionsToCheck = null, + IContent contentItem = null, + bool ignoreUserStartNodes = false) + { + if (storage == null) throw new ArgumentNullException("storage"); + if (user == null) throw new ArgumentNullException("user"); + if (userService == null) throw new ArgumentNullException("userService"); + if (contentService == null) throw new ArgumentNullException("contentService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) + { + contentItem = contentService.GetById(nodeId); + //put the content item into storage so it can be retreived + // in the controller (saves a lookup) + storage[typeof(IContent).ToString()] = contentItem; + } + + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + if(ignoreUserStartNodes) + { + return true; + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? user.HasContentRootAccess(entityService) + : (nodeId == Constants.System.RecycleBinContent) + ? user.HasContentBinAccess(entityService) + : user.HasPathAccess(contentItem, entityService); + + if (hasPathAccess == false) + { + return false; + } + + if (permissionsToCheck == null || permissionsToCheck.Length == 0) + { + return true; + } + + //get the implicit/inherited permissions for the user for this path, + //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) + var path = contentItem != null ? contentItem.Path : nodeId.ToString(); + var permission = userService.GetPermissionsForPath(user, path); + + var allowed = true; + foreach (var p in permissionsToCheck) + { + if (permission == null + || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) + { + allowed = false; + } + } + return allowed; + } + + [EnsureUserPermissionForContent("contentId", 'F')] + public IEnumerable GetNotificationOptions(int contentId) + { + var notifications = new List(); + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var content = Services.ContentService.GetById(contentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var actionList = ActionsResolver.Current.Actions; + foreach (var a in actionList) + { + if (a.ShowInNotifier) + { + NotifySetting n = new NotifySetting + { + Name = ui.Text("actions", a.Alias), + Checked = (UmbracoUser.GetNotifications(content.Path).IndexOf(a.Letter) > -1), + NotifyCode = a.Letter.ToString() + }; + notifications.Add(n); + } + } + return notifications; + } + + public void PostNotificationOptions(int contentId, string notifyOptions = "") + { + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var content = Services.ContentService.GetById(contentId); + + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var node = new CMSNode(contentId); + + global::umbraco.cms.businesslogic.workflow.Notification.UpdateNotifications(UmbracoUser, node, notifyOptions ?? ""); + } + } +} diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index a3f76db4f2..36f4465e96 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -1,999 +1,1004 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Net; -using System.Text; -using System.Web.Http; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using System.Linq; -using System.Net.Http; -using Umbraco.Core.Models; -using Constants = Umbraco.Core.Constants; -using Examine; -using Umbraco.Web.Dynamics; -using System.Text.RegularExpressions; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using System.Web.Http.Controllers; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Xml; -using Umbraco.Web.Search; -using Umbraco.Web.Trees; - -namespace Umbraco.Web.Editors -{ - /// - /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode - /// - /// - /// Some objects such as macros are not based on CMSNode - /// - [EntityControllerConfiguration] - [PluginController("UmbracoApi")] - public class EntityController : UmbracoAuthorizedJsonController - { - - /// - /// Configures this controller with a custom action selector - /// - private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - - //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" - //id is passed in eventually we'll probably want to support GUID + Udi too - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); - } - } - - private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); - - /// - /// Returns an Umbraco alias given a string - /// - /// - /// - /// - public dynamic GetSafeAlias(string value, bool camelCase = true) - { - var returnValue = (string.IsNullOrWhiteSpace(value)) ? string.Empty : value.ToSafeAlias(camelCase); - dynamic returnObj = new System.Dynamic.ExpandoObject(); - returnObj.alias = returnValue; - returnObj.original = value; - returnObj.camelCase = camelCase; - - return returnObj; - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) - { - return Search(query, type, false, searchFrom); - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, bool? ignoreUserStartNodes, string searchFrom = null) - { - //TODO: Should we restrict search results based on what app the user has access to? - // - Theoretically you shouldn't be able to see member data if you don't have access to members right? - - if (string.IsNullOrEmpty(query)) - return Enumerable.Empty(); - - return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value); - } - - /// - /// Searches for all content that the user is allowed to see (based on their allowed sections) - /// - /// - /// - /// - /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need - /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search - /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. - /// - /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search - /// methods might be used in things like pickers in the content editor. - /// - [HttpGet] - public IDictionary SearchAll(string query) - { - var result = new Dictionary(); - - if (string.IsNullOrEmpty(query)) - return result; - - var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); - var searchableTrees = SearchableTreeResolver.Current.GetSearchableTrees(); - - foreach (var searchableTree in searchableTrees) - { - if (allowedSections.Contains(searchableTree.Value.AppAlias)) - { - var tree = Services.ApplicationTreeService.GetByAlias(searchableTree.Key); - if (tree == null) continue; //shouldn't occur - - var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); - var treeAttribute = tree.GetTreeAttribute(); - - long total; - - result[treeAttribute.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult - { - Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out total), - TreeAlias = searchableTree.Key, - AppAlias = searchableTree.Value.AppAlias, - JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName, - JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName - }; - } - } - return result; - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(int id, UmbracoEntityTypes type) - { - var foundContent = GetResultForId(id, type); - - return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) - { - var foundContent = GetResultForKey(id, type); - - return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetPath(guidUdi.Guid, type); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// Gets the url of an entity - /// - /// Int id of the entity to fetch URL for - /// The tpye of entity such as Document, Media, Member - /// The URL or path to the item - public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) - { - var returnUrl = string.Empty; - - if (type == UmbracoEntityTypes.Document) - { - var foundUrl = Umbraco.Url(id); - if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") - { - returnUrl = foundUrl; - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(returnUrl) - }; - } - } - - var ancestors = GetAncestors(id, type); - - //if content, skip the first node for replicating NiceUrl defaults - if(type == UmbracoEntityTypes.Document) { - ancestors = ancestors.Skip(1); - } - - returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name)); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(returnUrl) - }; - } - - [Obsolete("Use GetyById instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) - { - return GetResultForKey(id, type); - } - - /// - /// Gets an entity by a xpath query - /// - /// - /// - /// - /// - public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) - { - //TODO: Rename this!!! It's misleading, it should be GetByXPath - - - if (type != UmbracoEntityTypes.Document) - throw new ArgumentException("Get by query is only compatible with enitities of type Document"); - - - var q = ParseXPathQuery(query, nodeContextId); - var node = Umbraco.TypedContentSingleAtXPath(q); - - if (node == null) - return null; - - return GetById(node.Id, type); - } - - //PP: wip in progress on the query parser - private string ParseXPathQuery(string query, int id) - { - return UmbracoXPathPathSyntaxParser.ParseXPathQuery( - xpathExpression: query, - nodeContextId: id, - getPath: nodeid => - { - var ent = Services.EntityService.Get(nodeid); - return ent.Path.Split(',').Reverse(); - }, - publishedContentExists: i => Umbraco.TypedContent(i) != null); - } - - #region GetById - - /// - /// Gets an entity by it's id - /// - /// - /// - /// - public EntityBasic GetById(int id, UmbracoEntityTypes type) - { - return GetResultForId(id, type); - } - - /// - /// Gets an entity by it's key - /// - /// - /// - /// - public EntityBasic GetById(Guid id, UmbracoEntityTypes type) - { - return GetResultForKey(id, type); - } - - /// - /// Gets an entity by it's UDI - /// - /// - /// - /// - public EntityBasic GetById(Udi id, UmbracoEntityTypes type) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetResultForKey(guidUdi.Guid, type); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - #region GetByIds - /// - /// Get entities by integer ids - /// - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForIds(ids, type); - } - - /// - /// Get entities by GUID ids - /// - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForKeys(ids, type); - } - - /// - /// Get entities by UDIs - /// - /// - /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids. - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - if (ids.Length == 0) - { - return Enumerable.Empty(); - } - - //all udi types will need to be the same in this list so we'll determine by the first - //currently we only support GuidIdi for this method - - var guidUdi = ids[0] as GuidUdi; - if (guidUdi != null) - { - return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - [Obsolete("Use GetyByIds instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetByKeys([FromUri]Guid[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForKeys(ids, type); - } - - public IEnumerable GetChildren(int id, UmbracoEntityTypes type) - { - return GetResultForChildren(id, type); - } - - /// - /// Get paged child entities by id - /// - /// - /// - /// - /// - /// - /// - /// - /// - public PagedResult GetPagedChildren( - string id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - int intId; - - if (int.TryParse(id, out intId)) - { - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); - } - - Guid guidId; - if (Guid.TryParse(id, out guidId)) - { - //Not supported currently - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - Udi udiId; - if (Udi.TryParse(id, out udiId)) - { - //Not supported currently - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type - if (id == Constants.Conventions.MemberTypes.AllMembersListId) - { - //the EntityService can search paged members from the root - - intId = -1; - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); - } - - //the EntityService cannot search members of a certain type, this is currently not supported and would require - //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search - - long total; - var searchResult = _treeSearcher.ExamineSearch(Umbraco, filter ?? "", type, pageSize, pageNumber - 1, out total, id); - - return new PagedResult(total, pageNumber, pageSize) - { - Items = searchResult - }; - } - - /// - /// Get paged child entities by id - /// - /// - /// - /// - /// - /// - /// - /// - /// - public PagedResult GetPagedChildren( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - if (pageNumber <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - if (pageSize <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - - var objectType = ConvertToObjectType(type); - if (objectType.HasValue) - { - long totalRecords; - var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = entities.Select(Mapper.Map) - }; - - return pagedResult; - } - - //now we need to convert the unknown ones - switch (type) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); - } - } - - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - return GetPagedDescendants(id, type, pageNumber, pageSize, - false, orderBy, orderDirection, filter); - } - - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - bool ignoreUserStartNodes, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - if (pageNumber <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - if (pageSize <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - - var objectType = ConvertToObjectType(type); - if (objectType.HasValue) - { - IEnumerable entities; - long totalRecords; - - if (id == Constants.System.Root) - { - // root is special: we reduce it to start nodes - - int[] aids = null; - switch (type) - { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes - ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) - : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - } - else - { - entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - } - - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = entities.Select(Mapper.Map) - }; - - return pagedResult; - } - - //now we need to convert the unknown ones - switch (type) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); - } - } - - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) - { - return GetResultForAncestors(id, type, false); - } - - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, bool ignoreUserStartNodes) - { - return GetResultForAncestors(id, type, ignoreUserStartNodes); - } - - public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) - { - return GetResultForAll(type, postFilter, postFilterParams); - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) - { - long total; - return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); - } - - - private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Need to check for Object types that support hierarchic here, some might not. - - return Services.EntityService.GetChildren(id, objectType.Value) - .WhereNotNull() - .Select(Mapper.Map); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, bool ignoreUserStartNodes = false) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Need to check for Object types that support hierarchic here, some might not. - - var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - - if (ignoreUserStartNodes == false) - { - int[] aids = null; - switch (entityType) - { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - if (aids != null) - { - var lids = new List(); - var ok = false; - foreach (var i in ids) - { - if (ok) - { - lids.Add(i); - continue; - } - if (aids.Contains(i)) - { - lids.Add(i); - ok = true; - } - } - ids = lids.ToArray(); - } - } - - return ids.Length == 0 - ? Enumerable.Empty() - : Services.EntityService.GetAll(objectType.Value, ids) - .WhereNotNull() - .OrderBy(x => x.Level) - .Select(Mapper.Map); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - /// - /// Gets the result for the entity list based on the type - /// - /// - /// A string where filter that will filter the results dynamically with linq - optional - /// the parameters to fill in the string where filter - optional - /// - private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Should we order this by something ? - var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); - return ExecutePostFilter(entities, postFilter, postFilterParams); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Template: - var templates = Services.FileService.GetTemplates(); - var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); - return filteredTemplates.Select(Mapper.Map); - - case UmbracoEntityTypes.Macro: - //Get all macros from the macro service - var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); - var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); - return filteredMacros.Select(Mapper.Map); - - case UmbracoEntityTypes.PropertyType: - - //get all document types, then combine all property types into one list - var propertyTypes = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) - .ToArray() - .SelectMany(x => x.PropertyTypes) - .DistinctBy(composition => composition.Alias); - var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredPropertyTypes); - - case UmbracoEntityTypes.PropertyGroup: - - //get all document types, then combine all property types into one list - var propertyGroups = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) - .ToArray() - .SelectMany(x => x.PropertyGroups) - .DistinctBy(composition => composition.Name); - var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredpropertyGroups); - - case UmbracoEntityTypes.User: - - int total; - var users = Services.UserService.GetAll(0, int.MaxValue, out total); - var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredUsers); - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) - { - if (keys.Length == 0) - return Enumerable.Empty(); - - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var entities = Services.EntityService.GetAll(objectType.Value, keys) - .WhereNotNull() - .Select(Mapper.Map); - - // entities are in "some" order, put them back in order - var xref = entities.ToDictionary(x => x.Key); - var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); - - return result; - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) - { - if (ids.Length == 0) - return Enumerable.Empty(); - - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var entities = Services.EntityService.GetAll(objectType.Value, ids) - .WhereNotNull() - .Select(Mapper.Map); - - // entities are in "some" order, put them back in order - var xref = entities.ToDictionary(x => x.Id); - var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); - - return result; - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private EntityBasic GetResultForKey(Guid key, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var found = Services.EntityService.GetByKey(key, objectType.Value); - if (found == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return Mapper.Map(found); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - - case UmbracoEntityTypes.PropertyGroup: - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - case UmbracoEntityTypes.User: - - case UmbracoEntityTypes.Macro: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private EntityBasic GetResultForId(int id, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var found = Services.EntityService.Get(id, objectType.Value); - if (found == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return Mapper.Map(found); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - - case UmbracoEntityTypes.PropertyGroup: - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - case UmbracoEntityTypes.User: - - case UmbracoEntityTypes.Macro: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private static UmbracoObjectTypes? ConvertToObjectType(UmbracoEntityTypes entityType) - { - switch (entityType) - { - case UmbracoEntityTypes.Document: - return UmbracoObjectTypes.Document; - case UmbracoEntityTypes.Media: - return UmbracoObjectTypes.Media; - case UmbracoEntityTypes.MemberType: - return UmbracoObjectTypes.MediaType; - case UmbracoEntityTypes.MemberGroup: - return UmbracoObjectTypes.MemberGroup; - case UmbracoEntityTypes.ContentItem: - return UmbracoObjectTypes.ContentItem; - case UmbracoEntityTypes.MediaType: - return UmbracoObjectTypes.MediaType; - case UmbracoEntityTypes.DocumentType: - return UmbracoObjectTypes.DocumentType; - case UmbracoEntityTypes.Stylesheet: - return UmbracoObjectTypes.Stylesheet; - case UmbracoEntityTypes.Member: - return UmbracoObjectTypes.Member; - case UmbracoEntityTypes.DataType: - return UmbracoObjectTypes.DataType; - default: - //There is no UmbracoEntity conversion (things like Macros, Users, etc...) - return null; - } - } - - /// - /// Executes the post filter against a collection of objects - /// - /// - /// - /// - /// - /// - private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter, IDictionary postFilterParams) - { - //if a post filter is assigned then try to execute it - if (postFilter.IsNullOrWhiteSpace() == false) - { - return postFilterParams == null - ? entities.AsQueryable().Where(postFilter).ToArray() - : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); - - } - return entities; - } - - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Net; +using System.Text; +using System.Web.Http; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using System.Linq; +using System.Net.Http; +using Umbraco.Core.Models; +using Constants = Umbraco.Core.Constants; +using Examine; +using Umbraco.Web.Dynamics; +using System.Text.RegularExpressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using System.Web.Http.Controllers; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Xml; +using Umbraco.Web.Search; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode + /// + /// + /// Some objects such as macros are not based on CMSNode + /// + [EntityControllerConfiguration] + [PluginController("UmbracoApi")] + public class EntityController : UmbracoAuthorizedJsonController + { + + /// + /// Configures this controller with a custom action selector + /// + private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + + //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" + //id is passed in eventually we'll probably want to support GUID + Udi too + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); + } + } + + private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); + + /// + /// Returns an Umbraco alias given a string + /// + /// + /// + /// + public dynamic GetSafeAlias(string value, bool camelCase = true) + { + var returnValue = (string.IsNullOrWhiteSpace(value)) ? string.Empty : value.ToSafeAlias(camelCase); + dynamic returnObj = new System.Dynamic.ExpandoObject(); + returnObj.alias = returnValue; + returnObj.original = value; + returnObj.camelCase = camelCase; + + return returnObj; + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) + { + return Search(query, type, null, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// If set used to look up whether user and group start node permissions will be ignored. + /// + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, Guid? dataTypeId, string searchFrom = null) + { + //TODO: Should we restrict search results based on what app the user has access to? + // - Theoretically you shouldn't be able to see member data if you don't have access to members right? + + if (string.IsNullOrEmpty(query)) + return Enumerable.Empty(); + + var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes); + } + + /// + /// Searches for all content that the user is allowed to see (based on their allowed sections) + /// + /// + /// + /// + /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need + /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search + /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. + /// + /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search + /// methods might be used in things like pickers in the content editor. + /// + [HttpGet] + public IDictionary SearchAll(string query) + { + var result = new Dictionary(); + + if (string.IsNullOrEmpty(query)) + return result; + + var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); + var searchableTrees = SearchableTreeResolver.Current.GetSearchableTrees(); + + foreach (var searchableTree in searchableTrees) + { + if (allowedSections.Contains(searchableTree.Value.AppAlias)) + { + var tree = Services.ApplicationTreeService.GetByAlias(searchableTree.Key); + if (tree == null) continue; //shouldn't occur + + var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); + var treeAttribute = tree.GetTreeAttribute(); + + long total; + + result[treeAttribute.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult + { + Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out total), + TreeAlias = searchableTree.Key, + AppAlias = searchableTree.Value.AppAlias, + JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName, + JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName + }; + } + } + return result; + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(int id, UmbracoEntityTypes type) + { + var foundContent = GetResultForId(id, type); + + return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) + { + var foundContent = GetResultForKey(id, type); + + return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetPath(guidUdi.Guid, type); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Gets the url of an entity + /// + /// Int id of the entity to fetch URL for + /// The tpye of entity such as Document, Media, Member + /// The URL or path to the item + public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) + { + var returnUrl = string.Empty; + + if (type == UmbracoEntityTypes.Document) + { + var foundUrl = Umbraco.Url(id); + if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") + { + returnUrl = foundUrl; + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(returnUrl) + }; + } + } + + var ancestors = GetAncestors(id, type); + + //if content, skip the first node for replicating NiceUrl defaults + if(type == UmbracoEntityTypes.Document) { + ancestors = ancestors.Skip(1); + } + + returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name)); + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(returnUrl) + }; + } + + [Obsolete("Use GetyById instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) + { + return GetResultForKey(id, type); + } + + /// + /// Gets an entity by a xpath query + /// + /// + /// + /// + /// + public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) + { + //TODO: Rename this!!! It's misleading, it should be GetByXPath + + + if (type != UmbracoEntityTypes.Document) + throw new ArgumentException("Get by query is only compatible with enitities of type Document"); + + + var q = ParseXPathQuery(query, nodeContextId); + var node = Umbraco.TypedContentSingleAtXPath(q); + + if (node == null) + return null; + + return GetById(node.Id, type); + } + + //PP: wip in progress on the query parser + private string ParseXPathQuery(string query, int id) + { + return UmbracoXPathPathSyntaxParser.ParseXPathQuery( + xpathExpression: query, + nodeContextId: id, + getPath: nodeid => + { + var ent = Services.EntityService.Get(nodeid); + return ent.Path.Split(',').Reverse(); + }, + publishedContentExists: i => Umbraco.TypedContent(i) != null); + } + + #region GetById + + /// + /// Gets an entity by it's id + /// + /// + /// + /// + public EntityBasic GetById(int id, UmbracoEntityTypes type) + { + return GetResultForId(id, type); + } + + /// + /// Gets an entity by it's key + /// + /// + /// + /// + public EntityBasic GetById(Guid id, UmbracoEntityTypes type) + { + return GetResultForKey(id, type); + } + + /// + /// Gets an entity by it's UDI + /// + /// + /// + /// + public EntityBasic GetById(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetResultForKey(guidUdi.Guid, type); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + #region GetByIds + /// + /// Get entities by integer ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForIds(ids, type); + } + + /// + /// Get entities by GUID ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForKeys(ids, type); + } + + /// + /// Get entities by UDIs + /// + /// + /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids. + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + if (ids.Length == 0) + { + return Enumerable.Empty(); + } + + //all udi types will need to be the same in this list so we'll determine by the first + //currently we only support GuidIdi for this method + + var guidUdi = ids[0] as GuidUdi; + if (guidUdi != null) + { + return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + [Obsolete("Use GetyByIds instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetByKeys([FromUri]Guid[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForKeys(ids, type); + } + + public IEnumerable GetChildren(int id, UmbracoEntityTypes type) + { + return GetResultForChildren(id, type); + } + + /// + /// Get paged child entities by id + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedResult GetPagedChildren( + string id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + int intId; + + if (int.TryParse(id, out intId)) + { + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + } + + Guid guidId; + if (Guid.TryParse(id, out guidId)) + { + //Not supported currently + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + Udi udiId; + if (Udi.TryParse(id, out udiId)) + { + //Not supported currently + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type + if (id == Constants.Conventions.MemberTypes.AllMembersListId) + { + //the EntityService can search paged members from the root + + intId = -1; + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + } + + //the EntityService cannot search members of a certain type, this is currently not supported and would require + //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search + + long total; + var searchResult = _treeSearcher.ExamineSearch(Umbraco, filter ?? "", type, pageSize, pageNumber - 1, out total, id); + + return new PagedResult(total, pageNumber, pageSize) + { + Items = searchResult + }; + } + + /// + /// Get paged child entities by id + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedResult GetPagedChildren( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + long totalRecords; + var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + return GetPagedDescendants(id, type, pageNumber, pageSize, + null, orderBy, orderDirection, filter); + } + + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + Guid? dataTypeId, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + IEnumerable entities; + long totalRecords; + + if (id == Constants.System.Root) + { + // root is special: we reduce it to start nodes + + int[] aids = null; + switch (type) + { + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes + ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) + : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + } + else + { + entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + } + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + + private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) + { + return GetResultForAncestors(id, type, null); + } + + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, Guid? dataTypeId) + { + return GetResultForAncestors(id, type, dataTypeId); + } + + public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) + { + return GetResultForAll(type, postFilter, postFilterParams); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// If set to true, user and group start node permissions will be ignored. + /// + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) + { + long total; + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); + } + + + private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchic here, some might not. + + return Services.EntityService.GetChildren(id, objectType.Value) + .WhereNotNull() + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, Guid? dataTypeId = null) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchic here, some might not. + + var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + if (ignoreUserStartNodes == false) + { + int[] aids = null; + switch (entityType) + { + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) + { + if (ok) + { + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; + } + } + ids = lids.ToArray(); + } + } + + return ids.Length == 0 + ? Enumerable.Empty() + : Services.EntityService.GetAll(objectType.Value, ids) + .WhereNotNull() + .OrderBy(x => x.Level) + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + /// + /// Gets the result for the entity list based on the type + /// + /// + /// A string where filter that will filter the results dynamically with linq - optional + /// the parameters to fill in the string where filter - optional + /// + private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Should we order this by something ? + var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); + return ExecutePostFilter(entities, postFilter, postFilterParams); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.Template: + var templates = Services.FileService.GetTemplates(); + var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); + return filteredTemplates.Select(Mapper.Map); + + case UmbracoEntityTypes.Macro: + //Get all macros from the macro service + var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); + var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); + return filteredMacros.Select(Mapper.Map); + + case UmbracoEntityTypes.PropertyType: + + //get all document types, then combine all property types into one list + var propertyTypes = Services.ContentTypeService.GetAllContentTypes().Cast() + .Concat(Services.ContentTypeService.GetAllMediaTypes()) + .ToArray() + .SelectMany(x => x.PropertyTypes) + .DistinctBy(composition => composition.Alias); + var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredPropertyTypes); + + case UmbracoEntityTypes.PropertyGroup: + + //get all document types, then combine all property types into one list + var propertyGroups = Services.ContentTypeService.GetAllContentTypes().Cast() + .Concat(Services.ContentTypeService.GetAllMediaTypes()) + .ToArray() + .SelectMany(x => x.PropertyGroups) + .DistinctBy(composition => composition.Name); + var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredpropertyGroups); + + case UmbracoEntityTypes.User: + + int total; + var users = Services.UserService.GetAll(0, int.MaxValue, out total); + var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredUsers); + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) + { + if (keys.Length == 0) + return Enumerable.Empty(); + + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var entities = Services.EntityService.GetAll(objectType.Value, keys) + .WhereNotNull() + .Select(Mapper.Map); + + // entities are in "some" order, put them back in order + var xref = entities.ToDictionary(x => x.Key); + var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + + return result; + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) + { + if (ids.Length == 0) + return Enumerable.Empty(); + + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var entities = Services.EntityService.GetAll(objectType.Value, ids) + .WhereNotNull() + .Select(Mapper.Map); + + // entities are in "some" order, put them back in order + var xref = entities.ToDictionary(x => x.Id); + var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + + return result; + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private EntityBasic GetResultForKey(Guid key, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var found = Services.EntityService.GetByKey(key, objectType.Value); + if (found == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return Mapper.Map(found); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + + case UmbracoEntityTypes.PropertyGroup: + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + case UmbracoEntityTypes.User: + + case UmbracoEntityTypes.Macro: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private EntityBasic GetResultForId(int id, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var found = Services.EntityService.Get(id, objectType.Value); + if (found == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return Mapper.Map(found); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + + case UmbracoEntityTypes.PropertyGroup: + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + case UmbracoEntityTypes.User: + + case UmbracoEntityTypes.Macro: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private static UmbracoObjectTypes? ConvertToObjectType(UmbracoEntityTypes entityType) + { + switch (entityType) + { + case UmbracoEntityTypes.Document: + return UmbracoObjectTypes.Document; + case UmbracoEntityTypes.Media: + return UmbracoObjectTypes.Media; + case UmbracoEntityTypes.MemberType: + return UmbracoObjectTypes.MediaType; + case UmbracoEntityTypes.MemberGroup: + return UmbracoObjectTypes.MemberGroup; + case UmbracoEntityTypes.ContentItem: + return UmbracoObjectTypes.ContentItem; + case UmbracoEntityTypes.MediaType: + return UmbracoObjectTypes.MediaType; + case UmbracoEntityTypes.DocumentType: + return UmbracoObjectTypes.DocumentType; + case UmbracoEntityTypes.Stylesheet: + return UmbracoObjectTypes.Stylesheet; + case UmbracoEntityTypes.Member: + return UmbracoObjectTypes.Member; + case UmbracoEntityTypes.DataType: + return UmbracoObjectTypes.DataType; + default: + //There is no UmbracoEntity conversion (things like Macros, Users, etc...) + return null; + } + } + + /// + /// Executes the post filter against a collection of objects + /// + /// + /// + /// + /// + /// + private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter, IDictionary postFilterParams) + { + //if a post filter is assigned then try to execute it + if (postFilter.IsNullOrWhiteSpace() == false) + { + return postFilterParams == null + ? entities.AsQueryable().Where(postFilter).ToArray() + : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); + + } + return entities; + } + + } +} diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 6c0c293572..788ae3a0c7 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -1,1014 +1,1017 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Formatting; -using System.Text; -using System.Threading.Tasks; -using System.Web.Http; -using System.Web.Http.ModelBinding; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using System.Linq; -using System.Web.Http.Controllers; -using Umbraco.Web.WebApi.Binders; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; -using Umbraco.Core.Configuration; -using Umbraco.Web.UI; -using Notification = Umbraco.Web.Models.ContentEditing.Notification; -using Umbraco.Core.Persistence; -using Umbraco.Core.Configuration.UmbracoSettings; - -namespace Umbraco.Web.Editors -{ - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the media application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Constants.Applications.Media)] - [MediaControllerControllerConfiguration] - public class MediaController : ContentControllerBase - { - /// - /// Configures this controller with a custom action selector - /// - private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); - } - } - - /// - /// Constructor - /// - public MediaController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public MediaController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - /// - /// Gets an empty content item for the - /// - /// - /// - /// - [OutgoingEditorModelEvent] - public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) - { - var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, Security.CurrentUser.Id); - var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - /// - /// Returns an item to be used to display the recycle bin for media - /// - /// - public ContentItemDisplay GetRecycleBin() - { - var display = new ContentItemDisplay - { - Id = Constants.System.RecycleBinMedia, - Alias = "recycleBin", - ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinMedia - }; - - TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); - - return display; - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundContent == null) - { - HandleContentNotFound(id); - //HandleContentNotFound will throw an exception - return null; - } - return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(Guid id) - { - var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundContent == null) - { - HandleContentNotFound(id); - //HandleContentNotFound will throw an exception - return null; - } - return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetById(guidUdi.Guid); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// Return media for the specified ids - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundMedia = Services.MediaService.GetByIds(ids); - return foundMedia.Select(media => AutoMapperExtensions.MapWithUmbracoContext(media, UmbracoContext)); - } - - /// - /// Returns media items known to be of a "Folder" type - /// - /// - /// - [Obsolete("This is no longer used and shouldn't be because it performs poorly when there are a lot of media items")] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetChildFolders(int id = -1) - { - //we are only allowing a max of 500 to be returned here, if more is required it needs to be paged - var result = GetChildFolders(id, 1, 500); - return result.Items; - } - - /// - /// Returns a paged result of media items known to be of a "Folder" type - /// - /// - /// - /// - /// - public PagedResult> GetChildFolders(int id, int pageNumber, int pageSize) - { - //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... - //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" - var folderTypes = Services.ContentTypeService - .GetAllMediaTypes() - .Where(x => x.Alias.EndsWith("Folder")) - .Select(x => x.Id) - .ToArray(); - - if (folderTypes.Length == 0) - { - return new PagedResult>(0, pageNumber, pageSize); - } - - long total; - var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, "Name", Direction.Ascending, true, null, folderTypes.ToArray()); - - return new PagedResult>(total, pageNumber, pageSize) - { - Items = children.Select(Mapper.Map>) - }; - } - - /// - /// Returns the root media objects - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetRootMedia() - { - //TODO: Add permissions check! - - return Services.MediaService.GetRootMedia() - .Select(Mapper.Map>); - } - - #region GetChildren - - private int[] _userStartNodes; - protected int[] UserStartNodes - { - get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } - } - - /// - /// Returns the child media objects - using the entity INT id - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(int id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "", - bool ignoreUserStartNodes = false) - { - //if a request is made for the root node data but the user's start node is not the default, then - // we need to return their start nodes - if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false)) - { - if (pageNumber > 0) - return new PagedResult>(0, 0, 0); - var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); - if (nodes.Length == 0) - return new PagedResult>(0, 0, 0); - if (pageSize < nodes.Length) pageSize = nodes.Length; // bah - var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) - { - Items = nodes.Select(Mapper.Map>) - }; - return pr; - } - - // else proceed as usual - - long totalChildren; - IMedia[] children; - if (pageNumber > 0 && pageSize > 0) - { - children = Services.MediaService - .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemField, filter).ToArray(); - } - else - { - children = Services.MediaService.GetChildren(id).ToArray(); - totalChildren = children.Length; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children - .Select(Mapper.Map>); - - return pagedResult; - } - - /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead - /// Returns the child media objects - using the entity GUID id - /// - /// - /// - /// - /// - /// - /// - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Guid id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Returns the child media objects - using the entity GUID id - /// - /// - /// - /// - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Guid id, - bool ignoreUserStartNodes, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - var entity = Services.EntityService.GetByKey(id); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead - /// Returns the child media objects - using the entity UDI id - /// - /// - /// - /// - /// - /// - /// - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Udi id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Returns the child media objects - using the entity UDI id - /// - /// - /// - /// - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Udi id, - bool ignoreUserStartNodes, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - var entity = Services.EntityService.GetByKey(guidUdi.Guid); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); - } - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] - public PagedResult> GetChildren(string id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "", - bool ignoreUserStartNodes = false) - { - foreach (var type in new[] { typeof(int), typeof(Guid) }) - { - var parsed = id.TryConvertTo(type); - if (parsed) - { - //oooh magic! will auto select the right overload - return GetChildren((dynamic)parsed.Result); - } - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - [EnsureUserPermissionForMedia("id")] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundMedia = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundMedia == null) - { - return HandleContentNotFound(id, false); - } - - //if the current item is in the recycle bin - if (foundMedia.IsInRecycleBin() == false) - { - var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, Security.CurrentUser.Id); - if (moveResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - else - { - var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, Security.CurrentUser.Id); - if (deleteResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForMedia("move.Id")] - public HttpResponseMessage PostMove(MoveOrCopy move) - { - var toMove = ValidateMoveOrCopy(move); - var destinationParentID = move.ParentId; - var sourceParentID = toMove.ParentId; - - var moveResult = Services.MediaService.WithResult().Move(toMove, move.ParentId, Security.CurrentUser.Id); - - if (sourceParentID == destinationParentID) - { - return Request.CreateValidationErrorResponse(new SimpleNotificationModel(new Notification("",Services.TextService.Localize("media/moveToSameFolderFailed"),SpeechBubbleIcon.Error))); - } - if (moveResult == false) - { - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - else - { - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [MediaPostValidate] - [OutgoingEditorModelEvent] - public MediaItemDisplay PostSave( - [ModelBinder(typeof(MediaItemBinder))] - MediaItemSave contentItem) - { - //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). - if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) - { - foreach (var file in contentItem.UploadedFiles) - { - file.FileName = Path.GetFileName(file.FileName); - } - } - - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - - MapPropertyValues(contentItem); - - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) - && (contentItem.Action == ContentSaveAction.SaveNew)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw validation response - var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } - } - - //save the item - var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); - - //return the updated model - var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); - - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (saveStatus.Success) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editMediaSaved"), - Services.TextService.Localize("speechBubbles/editMediaSavedText")); - } - else - { - AddCancelMessage(display); - - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } - } - - break; - } - - return display; - } - - /// - /// Maps the property values to the persisted entity - /// - /// - protected override void MapPropertyValues(ContentBaseItemSave contentItem) - { - UpdateName(contentItem); - - //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); - } - - /// - /// Empties the recycle bin - /// - /// - [HttpDelete] - [HttpPost] - public HttpResponseMessage EmptyRecycleBin() - { - Services.MediaService.EmptyRecycleBin(Security.CurrentUser.Id); - - return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForMedia("sorted.ParentId")] - public HttpResponseMessage PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } - - var mediaService = base.ApplicationContext.Services.MediaService; - var sortedMedia = new List(); - try - { - sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById)); - - // Save Media with new sort order and update content xml in db accordingly - if (mediaService.Sort(sortedMedia) == false) - { - LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); - return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); - } - return Request.CreateResponse(HttpStatusCode.OK); - } - catch (Exception ex) - { - LogHelper.Error("Could not update media sort order", ex); - throw; - } - } - - public MediaItemDisplay PostAddFolder(PostedFolder folder) - { - var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true); - - var mediaService = ApplicationContext.Services.MediaService; - - var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(f, Security.CurrentUser.Id); - - return AutoMapperExtensions.MapWithUmbracoContext(f, UmbracoContext); - } - - /// - /// Used to submit a media file - /// - /// - /// - /// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data. - /// - [FileUploadCleanupFilter(false)] - public async Task PostAddFile() - { - if (Request.Content.IsMimeMultipartContent() == false) - { - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); - } - - var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); - //ensure it exists - Directory.CreateDirectory(root); - var provider = new MultipartFormDataStreamProvider(root); - - var result = await Request.Content.ReadAsMultipartAsync(provider); - - //must have a file - if (result.FileData.Count == 0) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //get the string json from the request - string currentFolderId = result.FormData["currentFolder"]; - int parentId = GetParentIdAsInt(currentFolderId, validatePermissions: true); - - var tempFiles = new PostedFiles(); - var mediaService = ApplicationContext.Services.MediaService; - - //in case we pass a path with a folder in it, we will create it and upload media to it. - if (result.FormData.ContainsKey("path")) - { - - var folders = result.FormData["path"].Split('/'); - - for (int i = 0; i < folders.Length - 1; i++) - { - var folderName = folders[i]; - IMedia folderMediaItem; - - //if uploading directly to media root and not a subfolder - if (parentId == -1) - { - //look for matching folder - folderMediaItem = - mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - else - { - //get current parent - var mediaRoot = mediaService.GetById(parentId); - - //if the media root is null, something went wrong, we'll abort - if (mediaRoot == null) - return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, - "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + - " returned null"); - - //look for matching folder - folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - //set the media root to the folder id so uploaded files will end there. - parentId = folderMediaItem.Id; - } - } - - //get the files - foreach (var file in result.FileData) - { - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); - var safeFileName = fileName.ToSafeFileName(); - var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); - - if (UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(ext)) - { - var mediaType = Constants.Conventions.MediaTypes.File; - - if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect) - { - if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) - { - mediaType = Constants.Conventions.MediaTypes.Image; - } - } - else - { - mediaType = result.FormData["contentTypeAlias"]; - } - - var mediaItemName = fileName.ToFriendlyName(); - - var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); - - var fileInfo = new FileInfo(file.LocalFileName); - var fs = fileInfo.OpenReadWithRetry(); - if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); - using (fs) - { - f.SetValue(Constants.Conventions.Media.File, fileName, fs); - } - - var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); - if (saveResult == false) - { - AddCancelMessage(tempFiles, - message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName, - localizeMessage: false); - } - else - { - tempFiles.UploadedFiles.Add(new ContentItemFile - { - FileName = fileName, - PropertyAlias = Constants.Conventions.Media.File, - TempFilePath = file.LocalFileName - }); - } - } - else - { - tempFiles.Notifications.Add(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("media/disallowedFileType"), - SpeechBubbleIcon.Warning)); - } - } - - //Different response if this is a 'blueimp' request - if (Request.GetQueryNameValuePairs().Any(x => x.Key == "origin")) - { - var origin = Request.GetQueryNameValuePairs().First(x => x.Key == "origin"); - if (origin.Value == "blueimp") - { - return Request.CreateResponse(HttpStatusCode.OK, - tempFiles, - //Don't output the angular xsrf stuff, blue imp doesn't like that - new JsonMediaTypeFormatter()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK, tempFiles); - } - - /// - /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT - /// - /// - /// - /// If true, this will check if the current user has access to the resolved integer parent id - /// and if that check fails an unauthorized exception will occur - /// - /// - private int GetParentIdAsInt(string parentId, bool validatePermissions) - { - int intParentId; - GuidUdi parentUdi; - - // test for udi - if (GuidUdi.TryParse(parentId, out parentUdi)) - { - parentId = parentUdi.Guid.ToString(); - } - - //if it's not an INT then we'll check for GUID - if (int.TryParse(parentId, out intParentId) == false) - { - // if a guid then try to look up the entity - Guid idGuid; - if (Guid.TryParse(parentId, out idGuid)) - { - var entity = Services.EntityService.GetByKey(idGuid); - if (entity != null) - { - intParentId = entity.Id; - } - else - { - throw new EntityNotFoundException(parentId, "The passed id doesn't exist"); - } - } - else - { - throw new HttpResponseException( - Request.CreateValidationErrorResponse("The request was not formatted correctly, the parentId is not an integer, Guid or UDI")); - } - } - - //ensure the user has access to this folder by parent id! - if (validatePermissions && CheckPermissions( - new Dictionary(), - Security.CurrentUser, - Services.MediaService, - Services.EntityService, - intParentId) == false) - { - throw new HttpResponseException(Request.CreateResponse( - HttpStatusCode.Forbidden, - new SimpleNotificationModel(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), - SpeechBubbleIcon.Warning)))); - } - - return intParentId; - } - - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private IMedia ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var mediaService = Services.MediaService; - var toMove = mediaService.GetById(model.Id); - if (toMove == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - if (model.ParentId < 0) - { - //cannot move if the content item is not allowed at the root - if (toMove.ContentType.AllowedAsRoot == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - } - else - { - var parent = mediaService.GetById(model.ParentId); - if (parent == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //check if the item is allowed under this one - if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - - // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - } - - return toMove; - } - - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// The storage to add the content item to so it can be reused - /// - /// - /// - /// The content to lookup, if the contentItem is not specified - /// Specifies the already resolved content item to check against, setting this ignores the nodeId - /// - internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, int nodeId, IMedia media = null) - { - if (storage == null) throw new ArgumentNullException("storage"); - if (user == null) throw new ArgumentNullException("user"); - if (mediaService == null) throw new ArgumentNullException("mediaService"); - if (entityService == null) throw new ArgumentNullException("entityService"); - - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - media = mediaService.GetById(nodeId); - //put the content item into storage so it can be retreived - // in the controller (saves a lookup) - storage[typeof(IMedia).ToString()] = media; - } - - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var hasPathAccess = (nodeId == Constants.System.Root) - ? user.HasMediaRootAccess(entityService) - : (nodeId == Constants.System.RecycleBinMedia) - ? user.HasMediaBinAccess(entityService) - : user.HasPathAccess(media, entityService); - - return hasPathAccess; - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Text; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.ModelBinding; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using System.Linq; +using System.Web.Http.Controllers; +using Umbraco.Web.WebApi.Binders; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Configuration; +using Umbraco.Web.UI; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; +using Umbraco.Core.Persistence; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Web.Editors +{ + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the media application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorize(Constants.Applications.Media)] + [MediaControllerControllerConfiguration] + public class MediaController : ContentControllerBase + { + /// + /// Configures this controller with a custom action selector + /// + private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); + } + } + + /// + /// Constructor + /// + public MediaController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public MediaController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + /// + /// Gets an empty content item for the + /// + /// + /// + /// + [OutgoingEditorModelEvent] + public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) + { + var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, Security.CurrentUser.Id); + var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + /// + /// Returns an item to be used to display the recycle bin for media + /// + /// + public ContentItemDisplay GetRecycleBin() + { + var display = new ContentItemDisplay + { + Id = Constants.System.RecycleBinMedia, + Alias = "recycleBin", + ParentId = -1, + Name = Services.TextService.Localize("general/recycleBin"), + ContentTypeAlias = "recycleBin", + CreateDate = DateTime.Now, + IsContainer = true, + Path = "-1," + Constants.System.RecycleBinMedia + }; + + TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); + + return display; + } + + /// + /// Gets the media item by id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundContent == null) + { + HandleContentNotFound(id); + //HandleContentNotFound will throw an exception + return null; + } + return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + } + + /// + /// Gets the media item by id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(Guid id) + { + var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundContent == null) + { + HandleContentNotFound(id); + //HandleContentNotFound will throw an exception + return null; + } + return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + } + + /// + /// Gets the media item by id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetById(guidUdi.Guid); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Return media for the specified ids + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundMedia = Services.MediaService.GetByIds(ids); + return foundMedia.Select(media => AutoMapperExtensions.MapWithUmbracoContext(media, UmbracoContext)); + } + + /// + /// Returns media items known to be of a "Folder" type + /// + /// + /// + [Obsolete("This is no longer used and shouldn't be because it performs poorly when there are a lot of media items")] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetChildFolders(int id = -1) + { + //we are only allowing a max of 500 to be returned here, if more is required it needs to be paged + var result = GetChildFolders(id, 1, 500); + return result.Items; + } + + /// + /// Returns a paged result of media items known to be of a "Folder" type + /// + /// + /// + /// + /// + public PagedResult> GetChildFolders(int id, int pageNumber, int pageSize) + { + //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... + //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" + var folderTypes = Services.ContentTypeService + .GetAllMediaTypes() + .Where(x => x.Alias.EndsWith("Folder")) + .Select(x => x.Id) + .ToArray(); + + if (folderTypes.Length == 0) + { + return new PagedResult>(0, pageNumber, pageSize); + } + + long total; + var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, "Name", Direction.Ascending, true, null, folderTypes.ToArray()); + + return new PagedResult>(total, pageNumber, pageSize) + { + Items = children.Select(Mapper.Map>) + }; + } + + /// + /// Returns the root media objects + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetRootMedia() + { + //TODO: Add permissions check! + + return Services.MediaService.GetRootMedia() + .Select(Mapper.Map>); + } + + #region GetChildren + + private int[] _userStartNodes; + protected int[] UserStartNodes + { + get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } + } + + /// + /// Returns the child media objects - using the entity INT id + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(int id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "", + Guid? dataTypeId = null) + { + //if a request is made for the root node data but the user's start node is not the default, then + // we need to return their start nodes + if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) + { + var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + if (ignoreUserStartNodes == false) + { + if (pageNumber > 0) + return new PagedResult>(0, 0, 0); + var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult>(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) + { + Items = nodes.Select(Mapper.Map>) + }; + return pr; + } + } + + // else proceed as usual + + long totalChildren; + IMedia[] children; + if (pageNumber > 0 && pageSize > 0) + { + children = Services.MediaService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + } + else + { + children = Services.MediaService.GetChildren(id).ToArray(); + totalChildren = children.Length; + } + + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children + .Select(Mapper.Map>); + + return pagedResult; + } + + /// + /// This method is obsolete, use the overload with dataTypeId instead + /// Returns the child media objects - using the entity GUID id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Guid id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Returns the child media objects - using the entity GUID id + /// + /// + /// + /// + /// + /// + /// + /// + /// If set used to lookup whether user and group start node permissions should be ignored. + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Guid id, + Guid? dataTypeId, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var entity = Services.EntityService.GetByKey(id); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// This method is obsolete, use the overload with dataTypeId instead + /// Returns the child media objects - using the entity UDI id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Udi id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Returns the child media objects - using the entity UDI id + /// + /// + /// + /// + /// + /// + /// + /// + /// If set used to lookup whether the user and group start node permissions will be ignored. + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Udi id, + Guid? dataTypeId, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + var entity = Services.EntityService.GetByKey(guidUdi.Guid); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public PagedResult> GetChildren(string id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + foreach (var type in new[] { typeof(int), typeof(Guid) }) + { + var parsed = id.TryConvertTo(type); + if (parsed) + { + //oooh magic! will auto select the right overload + return GetChildren((dynamic)parsed.Result); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + /// + /// Moves an item to the recycle bin, if it is already there then it will permanently delete it + /// + /// + /// + [EnsureUserPermissionForMedia("id")] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundMedia = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundMedia == null) + { + return HandleContentNotFound(id, false); + } + + //if the current item is in the recycle bin + if (foundMedia.IsInRecycleBin() == false) + { + var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, Security.CurrentUser.Id); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + else + { + var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, Security.CurrentUser.Id); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForMedia("move.Id")] + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = ValidateMoveOrCopy(move); + var destinationParentID = move.ParentId; + var sourceParentID = toMove.ParentId; + + var moveResult = Services.MediaService.WithResult().Move(toMove, move.ParentId, Security.CurrentUser.Id); + + if (sourceParentID == destinationParentID) + { + return Request.CreateValidationErrorResponse(new SimpleNotificationModel(new Notification("",Services.TextService.Localize("media/moveToSameFolderFailed"),SpeechBubbleIcon.Error))); + } + if (moveResult == false) + { + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + else + { + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [MediaPostValidate] + [OutgoingEditorModelEvent] + public MediaItemDisplay PostSave( + [ModelBinder(typeof(MediaItemBinder))] + MediaItemSave contentItem) + { + //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. + //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + //uploaded files to being *only* the actual file name (as it should be). + if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) + { + foreach (var file in contentItem.UploadedFiles) + { + file.FileName = Path.GetFileName(file.FileName); + } + } + + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid + + MapPropertyValues(contentItem); + + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (ModelState.IsValid == false) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) + && (contentItem.Action == ContentSaveAction.SaveNew)) + { + //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // add the modelstate to the outgoing object and throw validation response + var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } + } + + //save the item + var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); + + //return the updated model + var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); + + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + if (saveStatus.Success) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editMediaSaved"), + Services.TextService.Localize("speechBubbles/editMediaSavedText")); + } + else + { + AddCancelMessage(display); + + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + } + + break; + } + + return display; + } + + /// + /// Maps the property values to the persisted entity + /// + /// + protected override void MapPropertyValues(ContentBaseItemSave contentItem) + { + UpdateName(contentItem); + + //use the base method to map the rest of the properties + base.MapPropertyValues(contentItem); + } + + /// + /// Empties the recycle bin + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage EmptyRecycleBin() + { + Services.MediaService.EmptyRecycleBin(Security.CurrentUser.Id); + + return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForMedia("sorted.ParentId")] + public HttpResponseMessage PostSort(ContentSortOrder sorted) + { + if (sorted == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //if there's nothing to sort just return ok + if (sorted.IdSortOrder.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + var mediaService = base.ApplicationContext.Services.MediaService; + var sortedMedia = new List(); + try + { + sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById)); + + // Save Media with new sort order and update content xml in db accordingly + if (mediaService.Sort(sortedMedia) == false) + { + LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); + return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); + } + return Request.CreateResponse(HttpStatusCode.OK); + } + catch (Exception ex) + { + LogHelper.Error("Could not update media sort order", ex); + throw; + } + } + + public MediaItemDisplay PostAddFolder(PostedFolder folder) + { + var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true); + + var mediaService = ApplicationContext.Services.MediaService; + + var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(f, Security.CurrentUser.Id); + + return AutoMapperExtensions.MapWithUmbracoContext(f, UmbracoContext); + } + + /// + /// Used to submit a media file + /// + /// + /// + /// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data. + /// + [FileUploadCleanupFilter(false)] + public async Task PostAddFile() + { + if (Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await Request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //get the string json from the request + string currentFolderId = result.FormData["currentFolder"]; + int parentId = GetParentIdAsInt(currentFolderId, validatePermissions: true); + + var tempFiles = new PostedFiles(); + var mediaService = ApplicationContext.Services.MediaService; + + //in case we pass a path with a folder in it, we will create it and upload media to it. + if (result.FormData.ContainsKey("path")) + { + + var folders = result.FormData["path"].Split('/'); + + for (int i = 0; i < folders.Length - 1; i++) + { + var folderName = folders[i]; + IMedia folderMediaItem; + + //if uploading directly to media root and not a subfolder + if (parentId == -1) + { + //look for matching folder + folderMediaItem = + mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + else + { + //get current parent + var mediaRoot = mediaService.GetById(parentId); + + //if the media root is null, something went wrong, we'll abort + if (mediaRoot == null) + return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, + "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + + " returned null"); + + //look for matching folder + folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + //set the media root to the folder id so uploaded files will end there. + parentId = folderMediaItem.Id; + } + } + + //get the files + foreach (var file in result.FileData) + { + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); + var safeFileName = fileName.ToSafeFileName(); + var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); + + if (UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(ext)) + { + var mediaType = Constants.Conventions.MediaTypes.File; + + if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect) + { + if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) + { + mediaType = Constants.Conventions.MediaTypes.Image; + } + } + else + { + mediaType = result.FormData["contentTypeAlias"]; + } + + var mediaItemName = fileName.ToFriendlyName(); + + var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); + + var fileInfo = new FileInfo(file.LocalFileName); + var fs = fileInfo.OpenReadWithRetry(); + if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); + using (fs) + { + f.SetValue(Constants.Conventions.Media.File, fileName, fs); + } + + var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); + if (saveResult == false) + { + AddCancelMessage(tempFiles, + message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName, + localizeMessage: false); + } + else + { + tempFiles.UploadedFiles.Add(new ContentItemFile + { + FileName = fileName, + PropertyAlias = Constants.Conventions.Media.File, + TempFilePath = file.LocalFileName + }); + } + } + else + { + tempFiles.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("media/disallowedFileType"), + SpeechBubbleIcon.Warning)); + } + } + + //Different response if this is a 'blueimp' request + if (Request.GetQueryNameValuePairs().Any(x => x.Key == "origin")) + { + var origin = Request.GetQueryNameValuePairs().First(x => x.Key == "origin"); + if (origin.Value == "blueimp") + { + return Request.CreateResponse(HttpStatusCode.OK, + tempFiles, + //Don't output the angular xsrf stuff, blue imp doesn't like that + new JsonMediaTypeFormatter()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK, tempFiles); + } + + /// + /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT + /// + /// + /// + /// If true, this will check if the current user has access to the resolved integer parent id + /// and if that check fails an unauthorized exception will occur + /// + /// + private int GetParentIdAsInt(string parentId, bool validatePermissions) + { + int intParentId; + GuidUdi parentUdi; + + // test for udi + if (GuidUdi.TryParse(parentId, out parentUdi)) + { + parentId = parentUdi.Guid.ToString(); + } + + //if it's not an INT then we'll check for GUID + if (int.TryParse(parentId, out intParentId) == false) + { + // if a guid then try to look up the entity + Guid idGuid; + if (Guid.TryParse(parentId, out idGuid)) + { + var entity = Services.EntityService.GetByKey(idGuid); + if (entity != null) + { + intParentId = entity.Id; + } + else + { + throw new EntityNotFoundException(parentId, "The passed id doesn't exist"); + } + } + else + { + throw new HttpResponseException( + Request.CreateValidationErrorResponse("The request was not formatted correctly, the parentId is not an integer, Guid or UDI")); + } + } + + //ensure the user has access to this folder by parent id! + if (validatePermissions && CheckPermissions( + new Dictionary(), + Security.CurrentUser, + Services.MediaService, + Services.EntityService, + intParentId) == false) + { + throw new HttpResponseException(Request.CreateResponse( + HttpStatusCode.Forbidden, + new SimpleNotificationModel(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), + SpeechBubbleIcon.Warning)))); + } + + return intParentId; + } + + /// + /// Ensures the item can be moved/copied to the new location + /// + /// + /// + private IMedia ValidateMoveOrCopy(MoveOrCopy model) + { + if (model == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var mediaService = Services.MediaService; + var toMove = mediaService.GetById(model.Id); + if (toMove == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + if (model.ParentId < 0) + { + //cannot move if the content item is not allowed at the root + if (toMove.ContentType.AllowedAsRoot == false) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + } + else + { + var parent = mediaService.GetById(model.ParentId); + if (parent == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //check if the item is allowed under this one + if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() + .Any(x => x.Value == toMove.ContentType.Id) == false) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + + // Check on paths + if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + } + + return toMove; + } + + /// + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node + /// + /// The storage to add the content item to so it can be reused + /// + /// + /// + /// The content to lookup, if the contentItem is not specified + /// Specifies the already resolved content item to check against, setting this ignores the nodeId + /// + internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, int nodeId, IMedia media = null) + { + if (storage == null) throw new ArgumentNullException("storage"); + if (user == null) throw new ArgumentNullException("user"); + if (mediaService == null) throw new ArgumentNullException("mediaService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) + { + media = mediaService.GetById(nodeId); + //put the content item into storage so it can be retreived + // in the controller (saves a lookup) + storage[typeof(IMedia).ToString()] = media; + } + + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? user.HasMediaRootAccess(entityService) + : (nodeId == Constants.System.RecycleBinMedia) + ? user.HasMediaBinAccess(entityService) + : user.HasPathAccess(media, entityService); + + return hasPathAccess; + } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index 4ed46de135..f2174ab120 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -1,46 +1,51 @@ -using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Represents a content property to be saved - /// - [DataContract(Name = "property", Namespace = "")] - public class ContentPropertyBasic - { - /// - /// This is the cmsPropertyData ID - /// - /// - /// This is not really used for anything - /// - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int Id { get; set; } - - [DataMember(Name = "value")] - public object Value { get; set; } - - [DataMember(Name = "alias", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string Alias { get; set; } - - [DataMember(Name = "editor", IsRequired = false)] - public string Editor { get; set; } - - /// - /// Flags the property to denote that it can contain sensitive data - /// - [DataMember(Name = "isSensitive", IsRequired = false)] - public bool IsSensitive { get; set; } - - /// - /// Used internally during model mapping - /// - [IgnoreDataMember] - internal PropertyEditor PropertyEditor { get; set; } - - } -} +using System; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents a content property to be saved + /// + [DataContract(Name = "property", Namespace = "")] + public class ContentPropertyBasic + { + /// + /// This is the cmsPropertyData ID + /// + /// + /// This is not really used for anything + /// + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } + + [DataMember(Name = "dataTypeId", IsRequired = true)] + [Required] + public Guid DataTypeId { get; set; } + + [DataMember(Name = "value")] + public object Value { get; set; } + + [DataMember(Name = "alias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string Alias { get; set; } + + [DataMember(Name = "editor", IsRequired = false)] + public string Editor { get; set; } + + /// + /// Flags the property to denote that it can contain sensitive data + /// + [DataMember(Name = "isSensitive", IsRequired = false)] + public bool IsSensitive { get; set; } + + /// + /// Used internally during model mapping + /// + [IgnoreDataMember] + internal PropertyEditor PropertyEditor { get; set; } + + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index ccf22a8c34..02cf570f7d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -1,75 +1,78 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Creates a base generic ContentPropertyBasic from a Property - /// - /// - internal class ContentPropertyBasicConverter : ITypeConverter - where T : ContentPropertyBasic, new() - { - protected IDataTypeService DataTypeService { get; private set; } - - private static readonly List ComplexPropertyTypeAliases = new List {"Umbraco.NestedContent"}; - - public ContentPropertyBasicConverter(IDataTypeService dataTypeService) - { - DataTypeService = dataTypeService; - } - - /// - /// Assigns the PropertyEditor, Id, Alias and Value to the property - /// - /// - public virtual T Convert(ResolutionContext context) - { - var property = context.SourceValue as Property; - if (property == null) - throw new InvalidOperationException("Source value is not a property."); - - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); - if (editor == null) - { - LogHelper.Error>( - "No property editor found, converting to a Label", - new NullReferenceException("The property editor with alias " + - property.PropertyType.PropertyEditorAlias + " does not exist")); - - editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); - } - - var result = new T - { - Id = property.Id, - Alias = property.Alias, - PropertyEditor = editor, - Editor = editor.Alias - }; - - // if there's a set of property aliases specified, we will check if the current property's value should be mapped. - // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. - if (context.Options.Items.ContainsKey("IncludeProperties")) - { - var includeProperties = context.Options.Items["IncludeProperties"] as IEnumerable; - if (includeProperties != null && includeProperties.Contains(property.Alias) == false) - { - return result; - } - } - - // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. - result.Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService); - return result; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a base generic ContentPropertyBasic from a Property + /// + /// + internal class ContentPropertyBasicConverter : ITypeConverter + where T : ContentPropertyBasic, new() + { + protected IDataTypeService DataTypeService { get; private set; } + + private static readonly List ComplexPropertyTypeAliases = new List {"Umbraco.NestedContent"}; + + public ContentPropertyBasicConverter(IDataTypeService dataTypeService) + { + DataTypeService = dataTypeService; + } + + /// + /// Assigns the PropertyEditor, Id, Alias and Value to the property + /// + /// + public virtual T Convert(ResolutionContext context) + { + var property = context.SourceValue as Property; + if (property == null) + throw new InvalidOperationException("Source value is not a property."); + + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + if (editor == null) + { + LogHelper.Error>( + "No property editor found, converting to a Label", + new NullReferenceException("The property editor with alias " + + property.PropertyType.PropertyEditorAlias + " does not exist")); + + editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); + } + + var dataTypeDefinition = DataTypeService.GetDataTypeDefinitionById(property.PropertyType.DataTypeDefinitionId); + + var result = new T + { + Id = property.Id, + DataTypeId = dataTypeDefinition.Key, + Alias = property.Alias, + PropertyEditor = editor, + Editor = editor.Alias + }; + + // if there's a set of property aliases specified, we will check if the current property's value should be mapped. + // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. + if (context.Options.Items.ContainsKey("IncludeProperties")) + { + var includeProperties = context.Options.Items["IncludeProperties"] as IEnumerable; + if (includeProperties != null && includeProperties.Contains(property.Alias) == false) + { + return result; + } + } + + // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. + result.Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService); + return result; + } + } +} diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 2ea41ff128..f2f83d1133 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -1,401 +1,407 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using System.Web.Http.ModelBinding; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Web.Search; - -namespace Umbraco.Web.Trees -{ - /// - /// A base controller reference for non-attributed trees (un-registered). Developers should inherit from - /// TreeController. - /// - [AngularJsonOnlyConfiguration] - public abstract class TreeControllerBase : UmbracoAuthorizedApiController - { - protected TreeControllerBase() - { - } - - protected TreeControllerBase(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - - protected TreeControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) - { - } - - /// - /// The method called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// - protected abstract TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); - - /// - /// Returns the menu structure for the node - /// - /// - /// - /// - protected abstract MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); - - /// - /// The name to display on the root node - /// - public abstract string RootNodeDisplayName { get; } - - /// - /// Gets the current tree alias from the attribute assigned to it. - /// - public abstract string TreeAlias { get; } - - /// - /// Returns the root node for the tree - /// - /// - /// - public TreeNode GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var node = CreateRootNode(queryStrings); - - //add the tree alias to the root - node.AdditionalData["treeAlias"] = TreeAlias; - - AddQueryStringsToAdditionalData(node, queryStrings); - - //check if the tree is searchable and add that to the meta data as well - if (this is ISearchableTree) - { - node.AdditionalData.Add("searchable", "true"); - } - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog(queryStrings)) - { - node.RoutePath = "#"; - } - - OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); - - return node; - } - - /// - /// The action called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// JSON markup for jsTree - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// - public TreeNodeCollection GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var nodes = GetTreeNodes(id, queryStrings); - - foreach (var node in nodes) - { - AddQueryStringsToAdditionalData(node, queryStrings); - } - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog((queryStrings))) - { - foreach (var node in nodes) - { - node.RoutePath = "#"; - } - } - - //raise the event - OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); - - return nodes; - } - - /// - /// The action called to render the menu for a tree node - /// - /// - /// - /// - public MenuItemCollection GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var menu = GetMenuForNode(id, queryStrings); - //raise the event - OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); - return menu; - } - - /// - /// Helper method to create a root model for a tree - /// - /// - protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); - - var node = new TreeNode( - rootNodeAsString, - null, //this is a root node, there is no parent - Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), - Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) - { - HasChildren = true, - RoutePath = currApp, - Name = RootNodeDisplayName - }; - - return node; - } - - #region Create TreeNode methods - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title }; - return node; - } - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) - { - Name = title, - Icon = icon, - NodeType = TreeAlias - }; - return node; - } - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, string routePath) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; - return node; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(UmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) - { - var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, entity.ContentTypeIcon); - treeNode.Path = entity.Path; - treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, string icon, bool hasChildren) - { - var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); - treeNode.Path = entity.Path; - treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url - /// - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - treeNode.RoutePath = routePath; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - treeNode.RoutePath = routePath; - treeNode.Udi = udi; - return treeNode; - } - - #endregion - - /// - /// The AdditionalData of a node is always populated with the query string data, this method performs this - /// operation and ensures that special values are not inserted or that duplicate keys are not added. - /// - /// - /// - protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) - { - foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) - { - node.AdditionalData.Add(q.Key, q.Value); - } - } - - /// - /// If the request is for a dialog mode tree - /// - /// - /// - protected bool IsDialog(FormDataCollection queryStrings) - { - return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); - } - - /// - /// If the request should allows a user to choose nodes that they normally don't have access to - /// - /// - /// - protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) - { - return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); - } - - /// - /// An event that allows developers to modify the tree node collection that is being rendered - /// - /// - /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. - /// - public static event TypedEventHandler TreeNodesRendering; - - private static void OnTreeNodesRendering(TreeControllerBase instance, TreeNodesRenderingEventArgs e) - { - var handler = TreeNodesRendering; - if (handler != null) handler(instance, e); - } - - /// - /// An event that allows developer to modify the root tree node that is being rendered - /// - public static event TypedEventHandler RootNodeRendering; - - private static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) - { - var handler = RootNodeRendering; - if (handler != null) handler(instance, e); - } - - /// - /// An event that allows developers to modify the meun that is being rendered - /// - /// - /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. - /// - public static event TypedEventHandler MenuRendering; - - private static void OnMenuRendering(TreeControllerBase instance, MenuRenderingEventArgs e) - { - var handler = MenuRendering; - if (handler != null) handler(instance, e); - } - } -} +using System; +using System.Globalization; +using System.Linq; +using System.Net.Http.Formatting; +using System.Web.Http.ModelBinding; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Web.Search; + +namespace Umbraco.Web.Trees +{ + /// + /// A base controller reference for non-attributed trees (un-registered). Developers should inherit from + /// TreeController. + /// + [AngularJsonOnlyConfiguration] + public abstract class TreeControllerBase : UmbracoAuthorizedApiController + { + protected TreeControllerBase() + { + } + + protected TreeControllerBase(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + protected TreeControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) + { + } + + /// + /// The method called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// + /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + protected abstract TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); + + /// + /// Returns the menu structure for the node + /// + /// + /// + /// + protected abstract MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); + + /// + /// The name to display on the root node + /// + public abstract string RootNodeDisplayName { get; } + + /// + /// Gets the current tree alias from the attribute assigned to it. + /// + public abstract string TreeAlias { get; } + + /// + /// Returns the root node for the tree + /// + /// + /// + public TreeNode GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var node = CreateRootNode(queryStrings); + + //add the tree alias to the root + node.AdditionalData["treeAlias"] = TreeAlias; + + AddQueryStringsToAdditionalData(node, queryStrings); + + //check if the tree is searchable and add that to the meta data as well + if (this is ISearchableTree) + { + node.AdditionalData.Add("searchable", "true"); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog(queryStrings)) + { + node.RoutePath = "#"; + } + + OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); + + return node; + } + + /// + /// The action called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// JSON markup for jsTree + /// + /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + public TreeNodeCollection GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var nodes = GetTreeNodes(id, queryStrings); + + foreach (var node in nodes) + { + AddQueryStringsToAdditionalData(node, queryStrings); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog((queryStrings))) + { + foreach (var node in nodes) + { + node.RoutePath = "#"; + } + } + + //raise the event + OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); + + return nodes; + } + + /// + /// The action called to render the menu for a tree node + /// + /// + /// + /// + public MenuItemCollection GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var menu = GetMenuForNode(id, queryStrings); + //raise the event + OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); + return menu; + } + + /// + /// Helper method to create a root model for a tree + /// + /// + protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); + var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); + + var node = new TreeNode( + rootNodeAsString, + null, //this is a root node, there is no parent + Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), + Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) + { + HasChildren = true, + RoutePath = currApp, + Name = RootNodeDisplayName + }; + + return node; + } + + #region Create TreeNode methods + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title }; + return node; + } + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) + { + Name = title, + Icon = icon, + NodeType = TreeAlias + }; + return node; + } + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, string routePath) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; + return node; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(UmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) + { + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, entity.ContentTypeIcon); + treeNode.Path = entity.Path; + treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, string icon, bool hasChildren) + { + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); + treeNode.Path = entity.Path; + treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + /// + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + treeNode.RoutePath = routePath; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + treeNode.RoutePath = routePath; + treeNode.Udi = udi; + return treeNode; + } + + #endregion + + /// + /// The AdditionalData of a node is always populated with the query string data, this method performs this + /// operation and ensures that special values are not inserted or that duplicate keys are not added. + /// + /// + /// + protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) + { + foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) + { + node.AdditionalData.Add(q.Key, q.Value); + } + } + + /// + /// If the request is for a dialog mode tree + /// + /// + /// + protected bool IsDialog(FormDataCollection queryStrings) + { + return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); + } + + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); + if (dataTypeId.HasValue) + { + return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + } + + return false; + } + + /// + /// An event that allows developers to modify the tree node collection that is being rendered + /// + /// + /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. + /// + public static event TypedEventHandler TreeNodesRendering; + + private static void OnTreeNodesRendering(TreeControllerBase instance, TreeNodesRenderingEventArgs e) + { + var handler = TreeNodesRendering; + if (handler != null) handler(instance, e); + } + + /// + /// An event that allows developer to modify the root tree node that is being rendered + /// + public static event TypedEventHandler RootNodeRendering; + + private static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) + { + var handler = RootNodeRendering; + if (handler != null) handler(instance, e); + } + + /// + /// An event that allows developers to modify the meun that is being rendered + /// + /// + /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. + /// + public static event TypedEventHandler MenuRendering; + + private static void OnMenuRendering(TreeControllerBase instance, MenuRenderingEventArgs e) + { + var handler = MenuRendering; + if (handler != null) handler(instance, e); + } + } +} diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index c79b9f8781..20e02d1c42 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -1,15 +1,15 @@ -namespace Umbraco.Web.Trees -{ - /// - /// Common query string parameters used for tree query strings - /// - internal struct TreeQueryStringParameters - { - public const string IsDialog = "isDialog"; - public const string Application = "application"; - public const string StartNodeId = "startNodeId"; - public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; - //public const string OnNodeClick = "OnNodeClick"; - //public const string RenderParent = "RenderParent"; - } -} +namespace Umbraco.Web.Trees +{ + /// + /// Common query string parameters used for tree query strings + /// + internal struct TreeQueryStringParameters + { + public const string IsDialog = "isDialog"; + public const string Application = "application"; + public const string StartNodeId = "startNodeId"; + public const string DataTypeId = "dataTypeId"; + //public const string OnNodeClick = "OnNodeClick"; + //public const string RenderParent = "RenderParent"; + } +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 64fe9a4b65..57a7db4889 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -1,148 +1,154 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Editors; -using Umbraco.Web.Models.ContentEditing; -using umbraco.BusinessLogic.Actions; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// Auth filter to check if the current user has access to the content item (by id). - /// - /// - /// - /// This first checks if the user can access this based on their start node, and then checks node permissions - /// - /// By default the permission that is checked is browse but this can be specified in the ctor. - /// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params. - /// - public sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute - { - private readonly int? _nodeId; - private readonly string _paramName; - private readonly char? _permissionToCheck; - - /// - /// This constructor will only be able to test the start node access - /// - public EnsureUserPermissionForContentAttribute(int nodeId) - { - _nodeId = nodeId; - } - - public EnsureUserPermissionForContentAttribute(int nodeId, char permissionToCheck) - : this(nodeId) - { - _permissionToCheck = permissionToCheck; - } - - public EnsureUserPermissionForContentAttribute(string paramName) - { - if (string.IsNullOrWhiteSpace(paramName)) throw new ArgumentException("Value cannot be null or whitespace.", "paramName"); - - _paramName = paramName; - _permissionToCheck = ActionBrowse.Instance.Letter; - } - - public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) - : this(paramName) - { - _permissionToCheck = permissionToCheck; - } - - public override bool AllowMultiple - { - get { return true; } - } - - public override void OnActionExecuting(HttpActionContext actionContext) - { - if (UmbracoContext.Current.Security.CurrentUser == null) - { - //not logged in - throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); - } - - var ignoreUserStartNodes = actionContext.ActionArguments.ContainsKey("ignoreUserStartNodes") && - bool.Parse(actionContext.ActionArguments.GetValueAsString("ignoreUserStartNodes")); - - int nodeId; - if (_nodeId.HasValue == false) - { - var parts = _paramName.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries); - - if (actionContext.ActionArguments[parts[0]] == null) - { - throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); - } - - if (parts.Length == 1) - { - var argument = actionContext.ActionArguments[parts[0]].ToString(); - // if the argument is an int, it will parse and can be assigned to nodeId - // if might be a udi, so check that next - // otherwise treat it as a guid - unlikely we ever get here - if (int.TryParse(argument, out int parsedId)) - { - nodeId = parsedId; - } - else if (Udi.TryParse(argument, true, out Udi udi)) - { - nodeId = ApplicationContext.Current.Services.EntityService.GetIdForUdi(udi).Result; - } - else - { - Guid.TryParse(argument, out Guid key); - nodeId = ApplicationContext.Current.Services.EntityService.GetIdForKey(key, UmbracoObjectTypes.Document).Result; - } - } - else - { - //now we need to see if we can get the property of whatever object it is - var pType = actionContext.ActionArguments[parts[0]].GetType(); - var prop = pType.GetProperty(parts[1]); - if (prop == null) - { - throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); - } - nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]); - } - } - else - { - nodeId = _nodeId.Value; - } - - if (ContentController.CheckPermissions( - actionContext.Request.Properties, - UmbracoContext.Current.Security.CurrentUser, - ApplicationContext.Current.Services.UserService, - ApplicationContext.Current.Services.ContentService, - ApplicationContext.Current.Services.EntityService, - nodeId, - _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, - ignoreUserStartNodes: ignoreUserStartNodes)) - { - base.OnActionExecuting(actionContext); - } - else - { - throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); - } - - } - - - - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; +using umbraco.BusinessLogic.Actions; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Auth filter to check if the current user has access to the content item (by id). + /// + /// + /// + /// This first checks if the user can access this based on their start node, and then checks node permissions + /// + /// By default the permission that is checked is browse but this can be specified in the ctor. + /// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params. + /// + public sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute + { + private readonly int? _nodeId; + private readonly string _paramName; + private readonly char? _permissionToCheck; + + /// + /// This constructor will only be able to test the start node access + /// + public EnsureUserPermissionForContentAttribute(int nodeId) + { + _nodeId = nodeId; + } + + public EnsureUserPermissionForContentAttribute(int nodeId, char permissionToCheck) + : this(nodeId) + { + _permissionToCheck = permissionToCheck; + } + + public EnsureUserPermissionForContentAttribute(string paramName) + { + if (string.IsNullOrWhiteSpace(paramName)) throw new ArgumentException("Value cannot be null or whitespace.", "paramName"); + + _paramName = paramName; + _permissionToCheck = ActionBrowse.Instance.Letter; + } + + public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) + : this(paramName) + { + _permissionToCheck = permissionToCheck; + } + + public override bool AllowMultiple + { + get { return true; } + } + + public override void OnActionExecuting(HttpActionContext actionContext) + { + if (UmbracoContext.Current.Security.CurrentUser == null) + { + //not logged in + throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); + } + + var ignoreUserStartNodes = false; + + if (actionContext.ActionArguments.ContainsKey("dataTypeId") && + Guid.TryParse(actionContext.ActionArguments.GetValueAsString("dataTypeId"), out var dataTypeId)) + { + ignoreUserStartNodes = + ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId); + } + + int nodeId; + if (_nodeId.HasValue == false) + { + var parts = _paramName.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries); + + if (actionContext.ActionArguments[parts[0]] == null) + { + throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); + } + + if (parts.Length == 1) + { + var argument = actionContext.ActionArguments[parts[0]].ToString(); + // if the argument is an int, it will parse and can be assigned to nodeId + // if might be a udi, so check that next + // otherwise treat it as a guid - unlikely we ever get here + if (int.TryParse(argument, out int parsedId)) + { + nodeId = parsedId; + } + else if (Udi.TryParse(argument, true, out Udi udi)) + { + nodeId = ApplicationContext.Current.Services.EntityService.GetIdForUdi(udi).Result; + } + else + { + Guid.TryParse(argument, out Guid key); + nodeId = ApplicationContext.Current.Services.EntityService.GetIdForKey(key, UmbracoObjectTypes.Document).Result; + } + } + else + { + //now we need to see if we can get the property of whatever object it is + var pType = actionContext.ActionArguments[parts[0]].GetType(); + var prop = pType.GetProperty(parts[1]); + if (prop == null) + { + throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); + } + nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]); + } + } + else + { + nodeId = _nodeId.Value; + } + + if (ContentController.CheckPermissions( + actionContext.Request.Properties, + UmbracoContext.Current.Security.CurrentUser, + ApplicationContext.Current.Services.UserService, + ApplicationContext.Current.Services.ContentService, + ApplicationContext.Current.Services.EntityService, + nodeId, + _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, + ignoreUserStartNodes: ignoreUserStartNodes)) + { + base.OnActionExecuting(actionContext); + } + else + { + throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); + } + + } + + + + } +} diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 23d4fb871c..40f0ad2a79 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -8,13 +8,12 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; namespace Umbraco.Web.WebApi.Filters { /// - /// This inspects the result of the action that returns a collection of content and removes + /// This inspects the result of the action that returns a collection of content and removes /// any item that the current user doesn't have access to /// internal class FilterAllowedOutgoingMediaAttribute : ActionFilterAttribute @@ -79,12 +78,18 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes); + + Guid? dataTypeId = Guid.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.DataTypeId), out var temp) ? (Guid?)temp : null; + + if (dataTypeId.HasValue == false) return; + + var ignoreUserStartNodes = ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); if (ignoreUserStartNodes == false) { FilterBasedOnStartNode(items, user); } + } internal void FilterBasedOnStartNode(IList items, IUser user) From 5d4f2ec6973e7b55d714138abf7217d18d4008a8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Jun 2019 15:45:12 +1000 Subject: [PATCH 071/218] Fixes issue with ImageCropperTemplateExtensions since it wasn't re-mapped to the new method definition --- src/Umbraco.Web/ImageCropperTemplateExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index d7f457287a..656f1e05a2 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -130,7 +130,7 @@ namespace Umbraco.Web if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false) return string.Empty; - var mediaItemUrl = mediaItem.MediaUrl(propertyAlias); + var mediaItemUrl = mediaItem.MediaUrl(propertyAlias: propertyAlias); //get the default obj from the value converter var cropperValue = mediaItem.Value(propertyAlias); From cd2ce2486afc264974ae3b974cc633d4dcfd92a2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Jun 2019 15:46:16 +1000 Subject: [PATCH 072/218] Fixes debugger display attribute --- src/Umbraco.Web/Models/PublishedContentBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 008bf10504..148bab11c0 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Models /// /// This base class does which (a) consistently resolves and caches the Url, (b) provides an implementation /// for this[alias], and (c) provides basic content set management. - [DebuggerDisplay("{Content Id: {Id}}")] + [DebuggerDisplay("Content Id: {Id}")] public abstract class PublishedContentBase : IPublishedContent { #region ContentType From da1e2680cb640b98c15a1cc5a4aa60fa2ed2abd4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Jun 2019 15:47:47 +1000 Subject: [PATCH 073/218] Fixes ContentStore when it's building the cache from sources, adds TODO notes, questions, concerns, etc.... --- .../PublishedCache/NuCache/ContentStore.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index e59d332525..38dd0952b8 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -305,6 +305,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public void UpdateContentTypes(IEnumerable removedIds, IEnumerable refreshedTypes, IEnumerable kits) { + var isRebuilding = removedIds == null && kits == null; + var removedIdsA = removedIds?.ToArray() ?? Array.Empty(); var refreshedTypesA = refreshedTypes?.ToArray() ?? Array.Empty(); var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToArray(); @@ -315,6 +317,34 @@ namespace Umbraco.Web.PublishedCache.NuCache { Lock(lockInfo); + + if (isRebuilding) + { + //All calls to this method when these are null means that we are regenerating the cache and first only setting content types + //TODO: A different method should be used for this operation - there are 4 places where this is called with these two args as null + + //In this case we know we are regenerating everything, so clear it all + //TODO: From my understanding, we can't 'just' clear things like _contentNodes or _xmap, etc... since the cache can be in use on a diff generation? something like that. + + //TODO: Follow-up: Actually, in all cases where this is called with the 2 null params, a call is made to _contentStore.SetAll which performs this reset anyways so + // we don't really need to do much in this case at all. + + //ClearLocked(_contentNodes); + //ClearRootLocked(); + + //TODO: I'm unsure how this is handled behind the scenes - or maybe it simply doesn't need to be handled since it will never have a variying UDI -> ID map + // since the this mapping in the DB would never change? + //_xmap.Clear(); + + // At this point, all we need to do is perform update of refreshed content types + foreach (var type in refreshedTypesA) + { + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); + } + return; + } + var removedContentTypeNodes = new List(); var refreshedContentTypeNodes = new List(); @@ -552,6 +582,9 @@ namespace Umbraco.Web.PublishedCache.NuCache foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x))) { SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); + + //TODO: When this is called from LoadContentFromLocalDbLocked we are populating the cache from the localDB, + // so we shouldn't then need to spend the effort to tell it to update itself based on itself? if (_localDb != null) RegisterChange(kit.Node.Id, kit); AddNodeLocked(kit.Node); From 2870ce5e0a75ca3b0bb7479bfcc945cb7271fa47 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Jun 2019 15:48:10 +1000 Subject: [PATCH 074/218] a perf change that in ContentCacheRefresher it doesn't iterate payloads if there is no real Id --- src/Umbraco.Web/Cache/ContentCacheRefresher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs index e4d2c2e4da..21b97d980d 100644 --- a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Cache var idsRemoved = new HashSet(); var isolatedCache = AppCaches.IsolatedCaches.GetOrCreate(); - foreach (var payload in payloads) + foreach (var payload in payloads.Where(x => x.Id != default)) { isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Id)); From d129989c3df5db98c3b28ec46a4872568f06f215 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Jun 2019 16:07:07 +1000 Subject: [PATCH 075/218] Removes irrelevant GetAbsoluteMediaUrl method now that there is both a media url provider and urls have been refactored in this PR, this was overlooked with the media url provider PR --- src/Umbraco.Web/UrlHelperRenderExtensions.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs index 8fe0524209..5307aa5f86 100644 --- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -371,25 +371,6 @@ namespace Umbraco.Web return url.SurfaceAction(action, typeof (T), additionalRouteVals); } - /// - /// Generates a Absolute Media Item URL based on the current context - /// - /// - /// - /// - public static string GetAbsoluteMediaUrl(this UrlHelper urlHelper, IPublishedContent mediaItem) - { - if (urlHelper == null) throw new ArgumentNullException("urlHelper"); - if (mediaItem == null) throw new ArgumentNullException("mediaItem"); - - if (urlHelper.RequestContext.HttpContext.Request.Url != null) - { - var requestUrl = urlHelper.RequestContext.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority); - return string.Format("{0}{1}", requestUrl, mediaItem.Url()); - } - return null; - } - /// /// This is used in methods like BeginUmbracoForm and SurfaceAction to generate an encrypted string which gets submitted in a request for which /// Umbraco can decrypt during the routing process in order to delegate the request to a specific MVC Controller. From be9d8e7da72c3fc3d817c18b92789a516c0e3cc3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Jun 2019 16:08:54 +1000 Subject: [PATCH 076/218] null checks on UrlHelperRenderExtensions when requesting image crop urls, if there's a null we'll just return an empty string --- src/Umbraco.Web/UrlHelperRenderExtensions.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs index 6f7fbacf7a..ceb7f5e8a6 100644 --- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -20,6 +20,8 @@ namespace Umbraco.Web public static class UrlHelperRenderExtensions { + private static readonly IHtmlString EmptyHtmlString = new HtmlString(string.Empty); + #region GetCropUrl /// @@ -39,6 +41,8 @@ namespace Umbraco.Web /// public static IHtmlString GetCropUrl(this UrlHelper urlHelper, IPublishedContent mediaItem, string cropAlias, bool htmlEncode = true) { + if (mediaItem == null) return EmptyHtmlString; + var url = mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true); return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); } @@ -65,6 +69,8 @@ namespace Umbraco.Web /// public static IHtmlString GetCropUrl(this UrlHelper urlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias, bool htmlEncode = true) { + if (mediaItem == null) return EmptyHtmlString; + var url = mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url); } @@ -144,6 +150,8 @@ namespace Umbraco.Web bool upScale = true, bool htmlEncode = true) { + if (mediaItem == null) return EmptyHtmlString; + var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, upScale); @@ -247,6 +255,8 @@ namespace Umbraco.Web bool upScale = true, bool htmlEncode = true) { + if (imageCropperValue == null) return EmptyHtmlString; + var imageUrl = imageCropperValue.Src; var url = imageUrl.GetCropUrl(imageCropperValue, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, From b55d2a8a41c96f9e6461157f45c7f8df27d64733 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 09:55:46 +0200 Subject: [PATCH 077/218] Modelsbuilder is asking for too old a version of the CodeAnalysis dependency --- build/NuSpecs/UmbracoCms.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 8b650edc13..7d864fb03a 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,6 +22,7 @@ + From ad4416f6633c95520d9dff8f009d2f43f5093b26 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 10:07:45 +0200 Subject: [PATCH 078/218] So apparently Microsoft.CodeAnalysis.CSharp version 1.3.2 contains the dll that has the version 1.3.1.. very confusing --- build/NuSpecs/UmbracoCms.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 7d864fb03a..838ec5eff0 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,7 +22,7 @@ - + From 5ebdfc7b1ce7e9329af64e51f1004a481a8fefb4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 10:22:16 +0200 Subject: [PATCH 079/218] Previously added a few too many binding redirects to the web.config --- src/Umbraco.Web.UI/web.Template.config | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 583952f29d..4ff70db974 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -438,22 +438,6 @@ - - - - - - - - - - - - - - - - From 2898203cb9d21acfb3430f5b04a50e991de414b8 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jun 2019 10:35:05 +0200 Subject: [PATCH 080/218] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 Bugfixes for the "Bypass security" feature + filter out possible sensitive data --- .../src/common/resources/content.resource.js | 6 +++- .../src/common/resources/entity.resource.js | 15 +++++--- .../contentpicker/contentpicker.controller.js | 2 +- .../ContentEditing/ContentPropertyBasic.cs | 5 ++- ...EnsureUserPermissionForContentAttribute.cs | 17 +++++++--- .../FilterAllowedOutgoingMediaAttribute.cs | 34 +++++++++++++++++++ 6 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 31eccfb5aa..d85cc74d7d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -331,12 +331,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { //now copy back to the options we will use options = defaults; + var args = [{ id: id }]; + if(options.dataTypeId){ + args.push({ dataTypeId: options.dataTypeId }); + } return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }, { dataTypeId: options.dataTypeId }])), + args)), 'Failed to retrieve data for content id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index fff4ddde37..3991932d2d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -304,16 +304,21 @@ function entityResource($q, $http, umbRequestHelper) { //now copy back to the options we will use options = defaults; + + var args = [ + { id: id }, + { type: type }, + ]; + if(options.dataTypeId){ + args.push({dataTypeId: options.dataTypeId}); + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [ - { id: id }, - { type: type }, - { dataTypeId: options.dataTypeId } - ])), + args)), 'Failed to retrieve ancestor data for id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 627baa3e7a..187b86a811 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -133,7 +133,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper idType: "int" }; - dialogOptions.dataTypeId = $scope.model.dataTypeId; //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the // pre-value config on to the dialog options angular.extend(dialogOptions, $scope.model.config); @@ -184,6 +183,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.contentPickerOverlay = dialogOptions; $scope.contentPickerOverlay.view = "treepicker"; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; $scope.contentPickerOverlay.submit = function(model) { diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index f2174ab120..9edbab04ae 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -21,9 +21,8 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public int Id { get; set; } - [DataMember(Name = "dataTypeId", IsRequired = true)] - [Required] - public Guid DataTypeId { get; set; } + [DataMember(Name = "dataTypeId", IsRequired = false)] + public Guid? DataTypeId { get; set; } [DataMember(Name = "value")] public object Value { get; set; } diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 57a7db4889..dfdea268c4 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -75,11 +75,20 @@ namespace Umbraco.Web.WebApi.Filters var ignoreUserStartNodes = false; - if (actionContext.ActionArguments.ContainsKey("dataTypeId") && - Guid.TryParse(actionContext.ActionArguments.GetValueAsString("dataTypeId"), out var dataTypeId)) + if (actionContext.ActionArguments.ContainsKey("dataTypeId")) { - ignoreUserStartNodes = - ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId); + if (actionContext.ActionArguments.TryGetValue("dataTypeId", out var dataTypeIdValue)) + { + var dataTypeIdString = dataTypeIdValue?.ToString(); + if (string.IsNullOrEmpty(dataTypeIdString) == false && + Guid.TryParse(dataTypeIdString, out var dataTypeId)) + { + ignoreUserStartNodes = + ApplicationContext.Current.Services.DataTypeService + .IsDataTypeIgnoringUserStartNodes(dataTypeId); + } + } + } int nodeId; diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 40f0ad2a79..d054337fd9 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -89,9 +89,43 @@ namespace Umbraco.Web.WebApi.Filters { FilterBasedOnStartNode(items, user); } + else + { + FilterOutPossibleSensitiveData(items); + } } + /// + /// Removes all properties of the items except the umbracoFile. + /// + /// + private void FilterOutPossibleSensitiveData(IList items) + { + foreach (dynamic item in items) + { + + if (item.Properties != null) + { + if (item.Properties is IList properties) + // Iterate reverse, because we removed from the same list, such that the ordering of indexes not + // changes doing the iteration + for (var i = properties.Count - 1; i >= 0; i--) + { + dynamic property = properties[i]; + if (property.Alias != null && property.Alias is string) + { + var alias = property.Alias as string; + if (string.Equals(alias, Constants.Conventions.Media.File) == false) + { + properties.RemoveAt(i); + } + } + } + } + } + } + internal void FilterBasedOnStartNode(IList items, IUser user) { var toRemove = new List(); From 40d4a8fe6aa40a46a1b186b93f1e2dafcfcb84c8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 13:32:53 +0200 Subject: [PATCH 081/218] Modelsbuilder is asking for too old a version of CodeAnalysis, Immutable and Metadata dependencies --- build/NuSpecs/UmbracoCms.nuspec | 1 - src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 20 ++++++++++---------- src/Umbraco.Web.UI/packages.config | 10 +++++----- src/Umbraco.Web.UI/web.Template.config | 8 -------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 838ec5eff0..8b650edc13 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,7 +22,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 9d92d55388..a0624a2dd7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -155,11 +155,11 @@ ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - ..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll + + ..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll - - ..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + + ..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll @@ -204,8 +204,8 @@ System - - ..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + ..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll @@ -236,8 +236,8 @@ False False - - ..\packages\System.Reflection.Metadata.1.6.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + + ..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll @@ -1002,8 +1002,8 @@ - - + + 11.0 diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index abcaf6bf83..18cbfdb042 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -19,9 +19,9 @@ - - - + + + @@ -36,7 +36,7 @@ - - + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 4ff70db974..0d26b71a38 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -438,14 +438,6 @@ - - - - - - - - From c4c2082fa93aa663349551009e8b52a04f9e54e0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jun 2019 13:57:17 +0200 Subject: [PATCH 082/218] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Only allow "ignoreUserStartNodes" for custom data types and not the default build in. --- src/Umbraco.Core/Constants-DataTypes.cs | 295 ++++++++ src/Umbraco.Core/Models/DataTypeDefinition.cs | 477 ++++++------ .../Models/IDataTypeDefinition.cs | 53 +- .../Factories/DataTypeDefinitionFactory.cs | 196 ++--- .../Migrations/Initial/BaseDataCreation.cs | 684 +++++++++--------- src/Umbraco.Core/Services/DataTypeService.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 + .../Models/Mapping/PreValueDisplayResolver.cs | 173 ++--- .../ContentPicker2PropertyEditor.cs | 10 +- .../PropertyEditors/GridPropertyEditor.cs | 16 +- .../MediaPicker2PropertyEditor.cs | 4 +- .../MultiNodeTreePicker2PropertyEditor.cs | 12 +- .../MultiUrlPickerPropertyEditor.cs | 2 +- .../RelatedLinks2PropertyEditor.cs | 6 +- .../PropertyEditors/RichTextPreValueEditor.cs | 147 ++-- 15 files changed, 1217 insertions(+), 863 deletions(-) create mode 100644 src/Umbraco.Core/Constants-DataTypes.cs diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs new file mode 100644 index 0000000000..d5d0e99dab --- /dev/null +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -0,0 +1,295 @@ +using System; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. + /// + public static class DataTypes + { + + public static class ReservedPreValueKeys + { + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; + } + + /// + /// Guid for Content Picker as string + /// + public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59"; + + /// + /// Guid for Content Picker + /// + public static readonly Guid ContentPickerGuid = new Guid(ContentPicker); + + + /// + /// Guid for Member Picker as string + /// + public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"; + + /// + /// Guid for Member Picker + /// + public static readonly Guid MemberPickerGuid = new Guid(MemberPicker); + + + /// + /// Guid for Media Picker as string + /// + public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5"; + + /// + /// Guid for Media Picker + /// + public static readonly Guid MediaPickerGuid = new Guid(MediaPicker); + + + /// + /// Guid for Multiple Media Picker as string + /// + public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A"; + + /// + /// Guid for Multiple Media Picker + /// + public static readonly Guid MultipleMediaPickerGuid = new Guid(MultipleMediaPicker); + + + /// + /// Guid for Related Links as string + /// + public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F"; + + /// + /// Guid for Related Links + /// + public static readonly Guid RelatedLinksGuid = new Guid(RelatedLinks); + + + /// + /// Guid for Member as string + /// + public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda"; + + /// + /// Guid for Member + /// + public static readonly Guid MemberGuid = new Guid(Member); + + + /// + /// Guid for Image Cropper as string + /// + public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f"; + + /// + /// Guid for Image Cropper + /// + public static readonly Guid ImageCropperGuid = new Guid(ImageCropper); + + + /// + /// Guid for Tags as string + /// + public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549"; + + /// + /// Guid for Tags + /// + public static readonly Guid TagsGuid = new Guid(Tags); + + + /// + /// Guid for List View - Content as string + /// + public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"; + + /// + /// Guid for List View - Content + /// + public static readonly Guid ListViewContentGuid = new Guid(ListViewContent); + + + /// + /// Guid for List View - Media as string + /// + public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"; + + /// + /// Guid for List View - Media + /// + public static readonly Guid ListViewMediaGuid = new Guid(ListViewMedia); + + + /// + /// Guid for List View - Members as string + /// + public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D"; + + /// + /// Guid for List View - Members + /// + public static readonly Guid ListViewMembersGuid = new Guid(ListViewMembers); + + + /// + /// Guid for Date Picker with time as string + /// + public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a"; + + /// + /// Guid for Date Picker with time + /// + public static readonly Guid DatePickerWithTimeGuid = new Guid(DatePickerWithTime); + + + /// + /// Guid for Approved Color as string + /// + public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17"; + + /// + /// Guid for Approved Color + /// + public static readonly Guid ApprovedColorGuid = new Guid(ApprovedColor); + + + /// + /// Guid for Dropdown multiple as string + /// + public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53"; + + /// + /// Guid for Dropdown multiple + /// + public static readonly Guid DropdownMultipleGuid = new Guid(DropdownMultiple); + + + /// + /// Guid for Radiobox as string + /// + public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805"; + + /// + /// Guid for Radiobox + /// + public static readonly Guid RadioboxGuid = new Guid(Radiobox); + + + /// + /// Guid for Date Picker as string + /// + public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1"; + + /// + /// Guid for Date Picker + /// + public static readonly Guid DatePickerGuid = new Guid(DatePicker); + + + /// + /// Guid for Dropdown as string + /// + public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid DropdownGuid = new Guid(Dropdown); + + + /// + /// Guid for Checkbox list as string + /// + public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a"; + + /// + /// Guid for Checkbox list + /// + public static readonly Guid CheckboxListGuid = new Guid(CheckboxList); + + + /// + /// Guid for Checkbox as string + /// + public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"; + + /// + /// Guid for Checkbox + /// + public static readonly Guid CheckboxGuid = new Guid(Checkbox); + + + /// + /// Guid for Numeric as string + /// + public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid NumericGuid = new Guid(Numeric); + + + /// + /// Guid for Richtext editor as string + /// + public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad"; + + /// + /// Guid for Richtext editor + /// + public static readonly Guid RichtextEditorGuid = new Guid(RichtextEditor); + + + /// + /// Guid for Textstring as string + /// + public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae"; + + /// + /// Guid for Textstring + /// + public static readonly Guid TextstringGuid = new Guid(Textstring); + + + /// + /// Guid for Textarea as string + /// + public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid TextareaGuid = new Guid(Textarea); + + + /// + /// Guid for Upload as string + /// + public const string Upload = "84c6b441-31df-4ffe-b67e-67d5bc3ae65a"; + + /// + /// Guid for Upload + /// + public static readonly Guid UploadGuid = new Guid(Upload); + + + /// + /// Guid for Label as string + /// + public const string Label = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; + + /// + /// Guid for Label + /// + public static readonly Guid LabelGuid = new Guid(Label); + + + } + } +} diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 3ba5125a90..5e1fd649ae 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -1,219 +1,260 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models -{ - /// - /// Definition of a DataType/PropertyEditor - /// - /// - /// The definition exists as a database reference between an actual DataType/PropertyEditor - /// (identified by its control id), its prevalues (configuration) and the named DataType in the backoffice UI. - /// - [Serializable] - [DataContract(IsReference = true)] - public class DataTypeDefinition : Entity, IDataTypeDefinition - { - private int _parentId; - private string _name; - private int _sortOrder; - private int _level; - private string _path; - private int _creatorId; - private bool _trashed; - private string _propertyEditorAlias; - private DataTypeDatabaseType _databaseType; - - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] - [EditorBrowsable(EditorBrowsableState.Never)] - public DataTypeDefinition(int parentId, Guid controlId) - { - _parentId = parentId; - - _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, false); - if (_propertyEditorAlias == null) - { - //convert to Label! - LogHelper.Warn("Could not find a GUID -> Alias mapping for the legacy property editor with id " + controlId + ". The DataType has been converted to a Label."); - _propertyEditorAlias = Constants.PropertyEditors.NoEditAlias; - } - - _additionalData = new Dictionary(); - } - - public DataTypeDefinition(int parentId, string propertyEditorAlias) - { - _parentId = parentId; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - } - - public DataTypeDefinition(string propertyEditorAlias) - { - _parentId = -1; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); - } - - /// - /// Gets or sets the Id of the Parent entity - /// - /// Might not be necessary if handled as a relation? - [DataMember] - public int ParentId - { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } - } - - /// - /// Gets or sets the name of the current entity - /// - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the sort order of the content entity - /// - [DataMember] - public int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } - } - - /// - /// Gets or sets the level of the content entity - /// - [DataMember] - public int Level - { - get { return _level; } - set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } - } - - /// - /// Gets or sets the path - /// - [DataMember] - public string Path //Setting this value should be handled by the class not the user - { - get { return _path; } - set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } - } - - /// - /// Id of the user who created this entity - /// - [DataMember] - public int CreatorId - { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } - } - - //NOTE: SD: Why do we have this ?? - - /// - /// Boolean indicating whether this entity is Trashed or not. - /// - [DataMember] - public bool Trashed - { - get { return _trashed; } - internal set - { - SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["Trashed"] = value; - } - } - - [DataMember] - public string PropertyEditorAlias - { - get { return _propertyEditorAlias; } - set - { - SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["DatabaseType"] = value; - } - } - - /// - /// Id of the DataType control - /// - [DataMember] - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")] - public Guid ControlId - { - get - { - return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias( - _propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value; - } - set - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); - PropertyEditorAlias = alias; - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["ControlId"] = value; - } - } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as - /// - [DataMember] - public DataTypeDatabaseType DatabaseType - { - get { return _databaseType; } - set - { - SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["DatabaseType"] = value; - } - } - - private readonly IDictionary _additionalData; - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - [EditorBrowsable(EditorBrowsableState.Never)] - IDictionary IUmbracoEntity.AdditionalData - { - get { return _additionalData; } - } - } +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models +{ + /// + /// Definition of a DataType/PropertyEditor + /// + /// + /// The definition exists as a database reference between an actual DataType/PropertyEditor + /// (identified by its control id), its prevalues (configuration) and the named DataType in the backoffice UI. + /// + [Serializable] + [DataContract(IsReference = true)] + public class DataTypeDefinition : Entity, IDataTypeDefinition + { + private int _parentId; + private string _name; + private int _sortOrder; + private int _level; + private string _path; + private int _creatorId; + private bool _trashed; + private string _propertyEditorAlias; + private DataTypeDatabaseType _databaseType; + + private static readonly ISet IdsOfBuildInDataTypes = new HashSet() + { + Constants.DataTypes.ContentPickerGuid, + Constants.DataTypes.MemberPickerGuid, + Constants.DataTypes.MediaPickerGuid, + Constants.DataTypes.MultipleMediaPickerGuid, + Constants.DataTypes.RelatedLinksGuid, + Constants.DataTypes.MemberGuid, + Constants.DataTypes.ImageCropperGuid, + Constants.DataTypes.TagsGuid, + Constants.DataTypes.ListViewContentGuid, + Constants.DataTypes.ListViewMediaGuid, + Constants.DataTypes.ListViewMembersGuid, + Constants.DataTypes.DatePickerWithTimeGuid, + Constants.DataTypes.ApprovedColorGuid, + Constants.DataTypes.DropdownMultipleGuid, + Constants.DataTypes.RadioboxGuid, + Constants.DataTypes.DatePickerGuid, + Constants.DataTypes.DropdownGuid, + Constants.DataTypes.CheckboxListGuid, + Constants.DataTypes.CheckboxGuid, + Constants.DataTypes.NumericGuid, + Constants.DataTypes.RichtextEditorGuid, + Constants.DataTypes.TextstringGuid, + Constants.DataTypes.TextareaGuid, + Constants.DataTypes.UploadGuid, + Constants.DataTypes.LabelGuid, + }; + + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] + [EditorBrowsable(EditorBrowsableState.Never)] + public DataTypeDefinition(int parentId, Guid controlId) + { + _parentId = parentId; + + _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, false); + if (_propertyEditorAlias == null) + { + //convert to Label! + LogHelper.Warn("Could not find a GUID -> Alias mapping for the legacy property editor with id " + controlId + ". The DataType has been converted to a Label."); + _propertyEditorAlias = Constants.PropertyEditors.NoEditAlias; + } + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(int parentId, string propertyEditorAlias) + { + _parentId = parentId; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(string propertyEditorAlias) + { + _parentId = -1; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(string propertyEditorAlias, Guid uniqueId) + { + _parentId = -1; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + IsBuildInDataType = IdsOfBuildInDataTypes.Contains(uniqueId); + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); + } + + /// + /// Gets or sets the Id of the Parent entity + /// + /// Might not be necessary if handled as a relation? + [DataMember] + public int ParentId + { + get { return _parentId; } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } + } + + /// + /// Gets or sets the name of the current entity + /// + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + /// + /// Gets or sets the sort order of the content entity + /// + [DataMember] + public int SortOrder + { + get { return _sortOrder; } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } + } + + /// + /// Gets or sets the level of the content entity + /// + [DataMember] + public int Level + { + get { return _level; } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } + } + + /// + /// Gets or sets the path + /// + [DataMember] + public string Path //Setting this value should be handled by the class not the user + { + get { return _path; } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } + } + + /// + /// Id of the user who created this entity + /// + [DataMember] + public int CreatorId + { + get { return _creatorId; } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } + } + + //NOTE: SD: Why do we have this ?? + + /// + /// Boolean indicating whether this entity is Trashed or not. + /// + [DataMember] + public bool Trashed + { + get { return _trashed; } + internal set + { + SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["Trashed"] = value; + } + } + + [DataMember] + public string PropertyEditorAlias + { + get { return _propertyEditorAlias; } + set + { + SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["DatabaseType"] = value; + } + } + + /// + /// Id of the DataType control + /// + [DataMember] + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")] + public Guid ControlId + { + get + { + return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias( + _propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value; + } + set + { + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); + PropertyEditorAlias = alias; + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["ControlId"] = value; + } + } + + /// + /// Gets or Sets the DatabaseType for which the DataType's value is saved as + /// + [DataMember] + public DataTypeDatabaseType DatabaseType + { + get { return _databaseType; } + set + { + SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["DatabaseType"] = value; + } + } + + private readonly IDictionary _additionalData; + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + [EditorBrowsable(EditorBrowsableState.Never)] + IDictionary IUmbracoEntity.AdditionalData + { + get { return _additionalData; } + } + + public bool IsBuildInDataType { get;} + } + } diff --git a/src/Umbraco.Core/Models/IDataTypeDefinition.cs b/src/Umbraco.Core/Models/IDataTypeDefinition.cs index a33736e604..f57a1bdca7 100644 --- a/src/Umbraco.Core/Models/IDataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/IDataTypeDefinition.cs @@ -1,24 +1,29 @@ -using System; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models -{ - public interface IDataTypeDefinition : IUmbracoEntity - { - /// - /// The Property editor alias assigned to the data type - /// - string PropertyEditorAlias { get; set; } - - /// - /// Id of the DataType control - /// - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead")] - Guid ControlId { get; set; } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as - /// - DataTypeDatabaseType DatabaseType { get; set; } - } -} \ No newline at end of file +using System; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + public interface IDataTypeDefinition : IUmbracoEntity + { + /// + /// The Property editor alias assigned to the data type + /// + string PropertyEditorAlias { get; set; } + + /// + /// Id of the DataType control + /// + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead")] + Guid ControlId { get; set; } + + /// + /// Gets or Sets the DatabaseType for which the DataType's value is saved as + /// + DataTypeDatabaseType DatabaseType { get; set; } + + /// + /// Gets information about whether this data type is build in or not. + /// + bool IsBuildInDataType { get;} + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index cf487fa1a2..14c162d8ea 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -1,98 +1,98 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class DataTypeDefinitionFactory - { - private readonly Guid _nodeObjectTypeId; - private int _primaryKey; - - public DataTypeDefinitionFactory(Guid nodeObjectTypeId) - { - _nodeObjectTypeId = nodeObjectTypeId; - } - - #region Implementation of IEntityFactory - - public IDataTypeDefinition BuildEntity(DataTypeDto dto) - { - var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias); - - - try - { - dataTypeDefinition.DisableChangeTracking(); - - dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; - dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); - dataTypeDefinition.Id = dto.DataTypeId; - dataTypeDefinition.Key = dto.NodeDto.UniqueId; - dataTypeDefinition.Level = dto.NodeDto.Level; - dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; - dataTypeDefinition.Name = dto.NodeDto.Text; - dataTypeDefinition.ParentId = dto.NodeDto.ParentId; - dataTypeDefinition.Path = dto.NodeDto.Path; - dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; - dataTypeDefinition.Trashed = dto.NodeDto.Trashed; - dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - dataTypeDefinition.ResetDirtyProperties(false); - return dataTypeDefinition; - } - finally - { - dataTypeDefinition.EnableChangeTracking(); - } - } - - public DataTypeDto BuildDto(IDataTypeDefinition entity) - { - var dataTypeDto = new DataTypeDto - { - PropertyEditorAlias = entity.PropertyEditorAlias, - DataTypeId = entity.Id, - DbType = entity.DatabaseType.ToString(), - NodeDto = BuildNodeDto(entity) - }; - - if (_primaryKey > 0) - { - dataTypeDto.PrimaryKey = _primaryKey; - } - - return dataTypeDto; - } - - #endregion - - public void SetPrimaryKey(int primaryKey) - { - _primaryKey = primaryKey; - } - - private NodeDto BuildNodeDto(IDataTypeDefinition entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - - return nodeDto; - } - } -} \ No newline at end of file +using System; +using System.Globalization; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class DataTypeDefinitionFactory + { + private readonly Guid _nodeObjectTypeId; + private int _primaryKey; + + public DataTypeDefinitionFactory(Guid nodeObjectTypeId) + { + _nodeObjectTypeId = nodeObjectTypeId; + } + + #region Implementation of IEntityFactory + + public IDataTypeDefinition BuildEntity(DataTypeDto dto) + { + var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias,dto.NodeDto.UniqueId); + + + try + { + dataTypeDefinition.DisableChangeTracking(); + + dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); + dataTypeDefinition.Id = dto.DataTypeId; + dataTypeDefinition.Key = dto.NodeDto.UniqueId; + dataTypeDefinition.Level = dto.NodeDto.Level; + dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.Name = dto.NodeDto.Text; + dataTypeDefinition.ParentId = dto.NodeDto.ParentId; + dataTypeDefinition.Path = dto.NodeDto.Path; + dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; + dataTypeDefinition.Trashed = dto.NodeDto.Trashed; + dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + dataTypeDefinition.ResetDirtyProperties(false); + return dataTypeDefinition; + } + finally + { + dataTypeDefinition.EnableChangeTracking(); + } + } + + public DataTypeDto BuildDto(IDataTypeDefinition entity) + { + var dataTypeDto = new DataTypeDto + { + PropertyEditorAlias = entity.PropertyEditorAlias, + DataTypeId = entity.Id, + DbType = entity.DatabaseType.ToString(), + NodeDto = BuildNodeDto(entity) + }; + + if (_primaryKey > 0) + { + dataTypeDto.PrimaryKey = _primaryKey; + } + + return dataTypeDto; + } + + #endregion + + public void SetPrimaryKey(int primaryKey) + { + _primaryKey = primaryKey; + } + + private NodeDto BuildNodeDto(IDataTypeDefinition entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = _nodeObjectTypeId, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; + + return nodeDto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index b8e605e63e..e1716947e1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -1,342 +1,342 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Migrations.Initial -{ - /// - /// Represents the initial data creation by running Insert for the base data. - /// - internal class BaseDataCreation - { - private readonly Database _database; - private readonly ILogger _logger; - - public BaseDataCreation(Database database, ILogger logger) - { - _database = database; - _logger = logger; - } - - /// - /// Initialize the base data creation by inserting the data foundation for umbraco - /// specific to a table - /// - /// Name of the table to create base data for - public void InitializeBaseData(string tableName) - { - _logger.Info(string.Format("Creating data in table {0}", tableName)); - - if(tableName.Equals("umbracoNode")) - { - CreateUmbracoNodeData(); - } - - if (tableName.Equals("umbracoLock")) - { - CreateUmbracoLockData(); - } - - if (tableName.Equals("cmsContentType")) - { - CreateCmsContentTypeData(); - } - - if (tableName.Equals("umbracoUser")) - { - CreateUmbracoUserData(); - } - - if (tableName.Equals("umbracoUserGroup")) - { - CreateUmbracoUserGroupData(); - } - - if (tableName.Equals("umbracoUser2UserGroup")) - { - CreateUmbracoUser2UserGroupData(); - } - - if (tableName.Equals("umbracoUserGroup2App")) - { - CreateUmbracoUserGroup2AppData(); - } - - if (tableName.Equals("cmsPropertyTypeGroup")) - { - CreateCmsPropertyTypeGroupData(); - } - - if (tableName.Equals("cmsPropertyType")) - { - CreateCmsPropertyTypeData(); - } - - if (tableName.Equals("umbracoLanguage")) - { - CreateUmbracoLanguageData(); - } - - if (tableName.Equals("cmsContentTypeAllowedContentType")) - { - CreateCmsContentTypeAllowedContentTypeData(); - } - - if(tableName.Equals("cmsDataType")) - { - CreateCmsDataTypeData(); - } - - if (tableName.Equals("cmsDataTypePreValues")) - { - CreateCmsDataTypePreValuesData(); - } - - if (tableName.Equals("umbracoRelationType")) - { - CreateUmbracoRelationTypeData(); - } - - if (tableName.Equals("cmsTaskType")) - { - CreateCmsTaskTypeData(); - } - - if (tableName.Equals("umbracoMigration")) - { - CreateUmbracoMigrationData(); - } - - _logger.Info(string.Format("Done creating data in table {0}", tableName)); - } - - private void CreateUmbracoNodeData() - { - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = new Guid("f0bc4bfb-b499-40d6-ba86-058885a5178c"), Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "Checkbox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); - - //New UDI pickers with newer Ids - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Related Links", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - } - - private void CreateUmbracoLockData() - { - // all lock objects - _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); - } - - private void CreateCmsContentTypeData() - { - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" }); - } - - private void CreateUmbracoUserData() - { - _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - } - - private void CreateUmbracoUserGroupData() - { - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); - } - - private void CreateUmbracoUser2UserGroupData() - { - _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins - _database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data - } - - private void CreateUmbracoUserGroup2AppData() - { - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); - } - - private void CreateCmsPropertyTypeGroupData() - { - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); - //membership property group - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); - } - - private void CreateCmsPropertyTypeData() - { - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - //membership property types - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); - - } - - private void CreateUmbracoLanguageData() - { - _database.Insert("umbracoLanguage", "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" }); - } - - private void CreateCmsContentTypeAllowedContentTypeData() - { - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); - } - - private void CreateCmsDataTypeData() - { - //TODO Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 1, DataTypeId = -49, PropertyEditorAlias = Constants.PropertyEditors.TrueFalseAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 2, DataTypeId = -51, PropertyEditorAlias = Constants.PropertyEditors.IntegerAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 3, DataTypeId = -87, PropertyEditorAlias = Constants.PropertyEditors.TinyMCEAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 4, DataTypeId = -88, PropertyEditorAlias = Constants.PropertyEditors.TextboxAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 5, DataTypeId = -89, PropertyEditorAlias = Constants.PropertyEditors.TextboxMultipleAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 6, DataTypeId = -90, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - - //New UDI pickers with newer Ids - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 26, DataTypeId = 1046, PropertyEditorAlias = Constants.PropertyEditors.ContentPicker2Alias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 27, DataTypeId = 1047, PropertyEditorAlias = Constants.PropertyEditors.MemberPicker2Alias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 28, DataTypeId = 1048, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 29, DataTypeId = 1049, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 30, DataTypeId = 1050, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinks2Alias, DbType = "Ntext" }); - } - - private void CreateCmsDataTypePreValuesData() - { - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 3, Alias = "", SortOrder = 0, DataTypeNodeId = -87, Value = ",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 4, Alias = "group", SortOrder = 0, DataTypeNodeId = 1041, Value = "default" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 5, Alias = "storageType", SortOrder = 0, DataTypeNodeId = 1041, Value = "Json" }); - - //defaults for the member list - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); - - //layouts for the list view - var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; - var listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; - - //defaults for the media list - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); - - //default's for MultipleMediaPickerAlias picker - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); - - // Defaults for single item dropdown - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 7, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -42, Value = "0" }); - - // Defaults for multiple item dropdown - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 8, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -39, Value = "1" }); - } - - private void CreateUmbracoRelationTypeData() - { - var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Media), ParentObjectType = new Guid(Constants.ObjectTypes.Media), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - } - - private void CreateCmsTaskTypeData() - { - _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); - } - - private void CreateUmbracoMigrationData() - { - var dto = new MigrationDto - { - Id = 1, - Name = Constants.System.UmbracoMigrationName, - Version = UmbracoVersion.GetSemanticVersion().ToString(), - CreateDate = DateTime.Now - }; - - _database.Insert("umbracoMigration", "pk", false, dto); - } - } -} +using System; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Migrations.Initial +{ + /// + /// Represents the initial data creation by running Insert for the base data. + /// + internal class BaseDataCreation + { + private readonly Database _database; + private readonly ILogger _logger; + + public BaseDataCreation(Database database, ILogger logger) + { + _database = database; + _logger = logger; + } + + /// + /// Initialize the base data creation by inserting the data foundation for umbraco + /// specific to a table + /// + /// Name of the table to create base data for + public void InitializeBaseData(string tableName) + { + _logger.Info(string.Format("Creating data in table {0}", tableName)); + + if(tableName.Equals("umbracoNode")) + { + CreateUmbracoNodeData(); + } + + if (tableName.Equals("umbracoLock")) + { + CreateUmbracoLockData(); + } + + if (tableName.Equals("cmsContentType")) + { + CreateCmsContentTypeData(); + } + + if (tableName.Equals("umbracoUser")) + { + CreateUmbracoUserData(); + } + + if (tableName.Equals("umbracoUserGroup")) + { + CreateUmbracoUserGroupData(); + } + + if (tableName.Equals("umbracoUser2UserGroup")) + { + CreateUmbracoUser2UserGroupData(); + } + + if (tableName.Equals("umbracoUserGroup2App")) + { + CreateUmbracoUserGroup2AppData(); + } + + if (tableName.Equals("cmsPropertyTypeGroup")) + { + CreateCmsPropertyTypeGroupData(); + } + + if (tableName.Equals("cmsPropertyType")) + { + CreateCmsPropertyTypeData(); + } + + if (tableName.Equals("umbracoLanguage")) + { + CreateUmbracoLanguageData(); + } + + if (tableName.Equals("cmsContentTypeAllowedContentType")) + { + CreateCmsContentTypeAllowedContentTypeData(); + } + + if(tableName.Equals("cmsDataType")) + { + CreateCmsDataTypeData(); + } + + if (tableName.Equals("cmsDataTypePreValues")) + { + CreateCmsDataTypePreValuesData(); + } + + if (tableName.Equals("umbracoRelationType")) + { + CreateUmbracoRelationTypeData(); + } + + if (tableName.Equals("cmsTaskType")) + { + CreateCmsTaskTypeData(); + } + + if (tableName.Equals("umbracoMigration")) + { + CreateUmbracoMigrationData(); + } + + _logger.Info(string.Format("Done creating data in table {0}", tableName)); + } + + private void CreateUmbracoNodeData() + { + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = Constants.DataTypes.LabelGuid, Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = Constants.DataTypes.UploadGuid, Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = Constants.DataTypes.TextareaGuid, Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = Constants.DataTypes.TextstringGuid, Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = Constants.DataTypes.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = Constants.DataTypes.NumericGuid, Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = Constants.DataTypes.CheckboxGuid, Text = "Checkbox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = Constants.DataTypes.CheckboxListGuid, Text = "Checkbox list", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = Constants.DataTypes.DropdownGuid, Text = "Dropdown", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = Constants.DataTypes.DatePickerGuid, Text = "Date Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = Constants.DataTypes.RadioboxGuid, Text = "Radiobox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = Constants.DataTypes.DropdownMultipleGuid, Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Constants.DataTypes.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = Constants.DataTypes.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewContentGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = Constants.DataTypes.TagsGuid, Text = "Tags", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = Constants.DataTypes.ImageCropperGuid, Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = Constants.DataTypes.MemberGuid, Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); + + //New UDI pickers with newer Ids + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = Constants.DataTypes.ContentPickerGuid, Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = Constants.DataTypes.MemberPickerGuid, Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = Constants.DataTypes.MediaPickerGuid, Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = Constants.DataTypes.MultipleMediaPickerGuid, Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = Constants.DataTypes.RelatedLinksGuid, Text = "Related Links", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + } + + private void CreateUmbracoLockData() + { + // all lock objects + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); + } + + private void CreateCmsContentTypeData() + { + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" }); + } + + private void CreateUmbracoUserData() + { + _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + } + + private void CreateUmbracoUserGroupData() + { + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); + } + + private void CreateUmbracoUser2UserGroupData() + { + _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins + _database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data + } + + private void CreateUmbracoUserGroup2AppData() + { + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); + } + + private void CreateCmsPropertyTypeGroupData() + { + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); + //membership property group + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); + } + + private void CreateCmsPropertyTypeData() + { + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + //membership property types + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); + + } + + private void CreateUmbracoLanguageData() + { + _database.Insert("umbracoLanguage", "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" }); + } + + private void CreateCmsContentTypeAllowedContentTypeData() + { + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); + } + + private void CreateCmsDataTypeData() + { + //TODO Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 1, DataTypeId = -49, PropertyEditorAlias = Constants.PropertyEditors.TrueFalseAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 2, DataTypeId = -51, PropertyEditorAlias = Constants.PropertyEditors.IntegerAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 3, DataTypeId = -87, PropertyEditorAlias = Constants.PropertyEditors.TinyMCEAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 4, DataTypeId = -88, PropertyEditorAlias = Constants.PropertyEditors.TextboxAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 5, DataTypeId = -89, PropertyEditorAlias = Constants.PropertyEditors.TextboxMultipleAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 6, DataTypeId = -90, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + + //New UDI pickers with newer Ids + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 26, DataTypeId = 1046, PropertyEditorAlias = Constants.PropertyEditors.ContentPicker2Alias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 27, DataTypeId = 1047, PropertyEditorAlias = Constants.PropertyEditors.MemberPicker2Alias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 28, DataTypeId = 1048, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 29, DataTypeId = 1049, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 30, DataTypeId = 1050, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinks2Alias, DbType = "Ntext" }); + } + + private void CreateCmsDataTypePreValuesData() + { + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 3, Alias = "", SortOrder = 0, DataTypeNodeId = -87, Value = ",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 4, Alias = "group", SortOrder = 0, DataTypeNodeId = 1041, Value = "default" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 5, Alias = "storageType", SortOrder = 0, DataTypeNodeId = 1041, Value = "Json" }); + + //defaults for the member list + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); + + //layouts for the list view + var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; + var listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; + + //defaults for the media list + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); + + //default's for MultipleMediaPickerAlias picker + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); + + // Defaults for single item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 7, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -42, Value = "0" }); + + // Defaults for multiple item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 8, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -39, Value = "1" }); + } + + private void CreateUmbracoRelationTypeData() + { + var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Media), ParentObjectType = new Guid(Constants.ObjectTypes.Media), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + } + + private void CreateCmsTaskTypeData() + { + _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); + } + + private void CreateUmbracoMigrationData() + { + var dto = new MigrationDto + { + Id = 1, + Name = Constants.System.UmbracoMigrationName, + Version = UmbracoVersion.GetSemanticVersion().ToString(), + CreateDate = DateTime.Now + }; + + _database.Insert("umbracoMigration", "pk", false, dto); + } + } +} diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 43c2b69c47..b02fb644d8 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -356,7 +356,7 @@ namespace Umbraco.Core.Services if (dataType != null) { var preValues = GetPreValuesCollectionByDataTypeId(dataType.Id); - if (preValues.PreValuesAsDictionary.TryGetValue("ignoreUserStartNodes", out var preValue)) + if (preValues.PreValuesAsDictionary.TryGetValue(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) { return string.Equals(preValue.Value, "1", StringComparison.InvariantCulture); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0b3c923c83..f29c1bea46 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -324,6 +324,9 @@ + + Constants.cs + diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index 9d1331e691..652bd34fff 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -1,84 +1,93 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - internal class PreValueDisplayResolver : ValueResolver> - { - private readonly IDataTypeService _dataTypeService; - - public PreValueDisplayResolver(IDataTypeService dataTypeService) - { - _dataTypeService = dataTypeService; +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class PreValueDisplayResolver : ValueResolver> + { + private readonly IDataTypeService _dataTypeService; + + public PreValueDisplayResolver(IDataTypeService dataTypeService) + { + _dataTypeService = dataTypeService; } - /// - /// Maps pre-values in the dictionary to the values for the fields. - /// - /// The fields. - /// The pre-values. - /// The editor alias. - internal static void MapPreValueValuesToPreValueFields(PreValueFieldDisplay[] fields, IDictionary preValues, string editorAlias) - { - if (fields == null) throw new ArgumentNullException(nameof(fields)); - if (preValues == null) throw new ArgumentNullException(nameof(preValues)); - - // Now we need to wire up the pre-values values with the actual fields defined - foreach (var field in fields) - { - // If the dictionary would be constructed with StringComparer.InvariantCultureIgnoreCase, we could just use TryGetValue - var preValue = preValues.SingleOrDefault(x => x.Key.InvariantEquals(field.Key)); - if (preValue.Key == null) - { - LogHelper.Warn("Could not find persisted pre-value for field {0} on property editor {1}", () => field.Key, () => editorAlias); - continue; - } - - field.Value = preValue.Value; - } - } - - internal IEnumerable Convert(IDataTypeDefinition source) - { - PropertyEditor propEd = null; - if (source.PropertyEditorAlias.IsNullOrWhiteSpace() == false) - { - propEd = PropertyEditorResolver.Current.GetByAlias(source.PropertyEditorAlias); - if (propEd == null) - { - throw new InvalidOperationException("Could not find property editor with alias " + source.PropertyEditorAlias); - } - } - - // Set up the defaults - var dataTypeService = _dataTypeService; - var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); - IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); - var result = Enumerable.Empty().ToArray(); - - // If we have a prop editor, then format the pre-values based on it and create it's fields - if (propEd != null) - { - result = propEd.PreValueEditor.Fields.Select(Mapper.Map).ToArray(); - dictionaryVals = propEd.PreValueEditor.ConvertDbToEditor(propEd.DefaultPreValues, preVals); - } - - MapPreValueValuesToPreValueFields(result, dictionaryVals, source.PropertyEditorAlias); - - return result; - } - - protected override IEnumerable ResolveCore(IDataTypeDefinition source) - { - return Convert(source); - } - } -} + /// + /// Maps pre-values in the dictionary to the values for the fields. + /// + /// The fields. + /// The pre-values. + /// The editor alias. + internal static void MapPreValueValuesToPreValueFields(IEnumerable fields, IDictionary preValues, string editorAlias) + { + if (fields == null) throw new ArgumentNullException(nameof(fields)); + if (preValues == null) throw new ArgumentNullException(nameof(preValues)); + + // Now we need to wire up the pre-values values with the actual fields defined + foreach (var field in fields) + { + // If the dictionary would be constructed with StringComparer.InvariantCultureIgnoreCase, we could just use TryGetValue + var preValue = preValues.SingleOrDefault(x => x.Key.InvariantEquals(field.Key)); + if (preValue.Key == null) + { + LogHelper.Warn("Could not find persisted pre-value for field {0} on property editor {1}", () => field.Key, () => editorAlias); + continue; + } + + field.Value = preValue.Value; + } + } + + internal IEnumerable Convert(IDataTypeDefinition source) + { + PropertyEditor propEd = null; + if (source.PropertyEditorAlias.IsNullOrWhiteSpace() == false) + { + propEd = PropertyEditorResolver.Current.GetByAlias(source.PropertyEditorAlias); + if (propEd == null) + { + throw new InvalidOperationException("Could not find property editor with alias " + source.PropertyEditorAlias); + } + } + + // Set up the defaults + var dataTypeService = _dataTypeService; + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); + IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); + var result = Enumerable.Empty(); + + // If we have a prop editor, then format the pre-values based on it and create it's fields + if (propEd != null) + { + result = propEd.PreValueEditor.Fields.Select(Mapper.Map).AsEnumerable(); + if (source.IsBuildInDataType) + { + result = RemovePreValuesNotSupportedOnBuildInTypes(result); + } + dictionaryVals = propEd.PreValueEditor.ConvertDbToEditor(propEd.DefaultPreValues, preVals); + } + + MapPreValueValuesToPreValueFields(result, dictionaryVals, source.PropertyEditorAlias); + + return result; + } + + private IEnumerable RemovePreValuesNotSupportedOnBuildInTypes(IEnumerable preValues) + { + return preValues.Where(preValue => string.Equals(preValue.Key, Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes) == false); + } + + protected override IEnumerable ResolveCore(IDataTypeDefinition source) + { + return Convert(source); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs index 0039385e5f..46f251ebf4 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs @@ -15,11 +15,11 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"startNodeId", "-1"}, + {"startNodeId", "-1"}, {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } @@ -40,9 +40,9 @@ namespace Umbraco.Web.PropertyEditors { public ContentPickerPreValueEditor() { - //create the fields + //create the fields Fields.Add(new PreValueField() - { + { Key = "showOpenButton", View = "boolean", Name = "Show open button (this feature is in preview!)", @@ -50,7 +50,7 @@ namespace Umbraco.Web.PropertyEditors }); Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 4a8803a099..94aed3e852 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Core.Constants.PropertyEditors.GridAlias, "Grid layout", "grid", HideLabel = true, IsParameterEditor = false, ValueType = PropertyEditorValueTypes.Json, Group="rich content", Icon="icon-layout")] public class GridPropertyEditor : PropertyEditor, IApplicationEventHandler - { + { private static void DocumentWriting(object sender, Examine.LuceneEngine.DocumentWritingEventArgs e) { @@ -49,7 +49,7 @@ namespace Umbraco.Web.PropertyEditors foreach (var areaVal in areaVals) { - //TODO: If it's not a string, then it's a json formatted value - + //TODO: If it's not a string, then it's a json formatted value - // we cannot really index this in a smart way since it could be 'anything' if (areaVal.Type == JTokenType.String) { @@ -87,12 +87,12 @@ namespace Umbraco.Web.PropertyEditors catch (InvalidCastException) { //swallow...on purpose, there's a chance that this isn't the json format we are looking for - // and we don't want that to affect the website. + // and we don't want that to affect the website. } catch (JsonException) { - //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect - // the website. + //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect + // the website. } catch (ArgumentException) { @@ -137,7 +137,7 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public string Rte { get; set; } - [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + [PreValueField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } @@ -152,7 +152,7 @@ namespace Umbraco.Web.PropertyEditors { /// /// We're going to bind to the Examine events so we can ensure grid data is index nicely. - /// + /// protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { foreach (var i in ExamineManager.Instance.IndexProviderCollection.OfType()) @@ -175,7 +175,7 @@ namespace Umbraco.Web.PropertyEditors public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { //wrap - _applicationStartup.OnApplicationStarted(umbracoApplication, applicationContext); + _applicationStartup.OnApplicationStarted(umbracoApplication, applicationContext); } #endregion } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs index 6aa7ab4a54..e62ac98abd 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.PropertyEditors } internal IDictionary InternalPreValues; - + public override IDictionary DefaultPreValues { get { return InternalPreValues; } @@ -58,7 +58,7 @@ namespace Umbraco.Web.PropertyEditors }); Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs index f57a9951b6..b79cd96313 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs @@ -16,11 +16,11 @@ namespace Umbraco.Web.PropertyEditors {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } - + protected override PreValueEditor CreatePreValueEditor() { return new MultiNodePickerPreValueEditor(); @@ -40,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors //create the fields Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." @@ -81,7 +81,7 @@ namespace Umbraco.Web.PropertyEditors Name = "Show open button (this feature is in preview!)", Description = "Opens the node in a dialog" }); - } + } /// /// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set @@ -117,9 +117,9 @@ namespace Umbraco.Web.PropertyEditors { result["multiPicker"] = "1"; } - } + } } - + return result; } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index f3ae317efa..ea3a1572b1 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.PropertyEditors { Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs index a96c0724ff..289e6588a9 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } @@ -33,11 +33,11 @@ namespace Umbraco.Web.PropertyEditors internal class RelatedLinksPreValueEditor : PreValueEditor { - [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + [PreValueField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int Maximum { get; set; } + public int Maximum { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs index 69445bc304..9fd2bf6de4 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs @@ -1,73 +1,74 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - //need to figure out how to use this... - internal class RichTextPreValueEditor : PreValueEditor - { - public RichTextPreValueEditor() - { - //SD: You can add pre-val fields here like you are doing, or you can add fields using attributes (http://issues.umbraco.org/issue/U4-2692), - // see below for examples. - - //use a custom editor too - Fields.Add(new PreValueField() - { - View = "views/propertyeditors/rte/rte.prevalues.html", - HideLabel = true, - Key = "editor" - }); - - Fields.Add(new PreValueField() - { - Key = "ignoreUserStartNodes", - View = "boolean", - Name = "Ignore user start nodes", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." - }); - - Fields.Add(new PreValueField() - { - Name = "Hide Label", - View = "boolean", - Key = "hideLabel" - }); - } - - //SD: You can declare a field like this if you want to instead of in the ctor, there's some options here: - //#1 - the property name becomes the Key: - // - // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public string Editor { get; set; } - - //#2 - You can specify a custom Key: - // - // [PreValueField("editor", "", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public string Editor { get; set; } - - //#3 - If you require custom server side validation for your field then you have to specify a custom PreValueField type to use, - // this is why in this case I find it easier to use the ctor logic but thats just an opinion - // - Any value specified for this property attribute will override the values set in on the class instance of the field, this - // allows you to re-use class instances of fields if you want. - // - // [PreValueField(typeof(EditorPreValueField))] - // public string Editor { get; set; } - - // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public class EditorPreValueField : PreValueField - // { - // public EditorPreValueField() - // { - // //add any required server validators for this field - // Validators.Add(new RegexValidator("^\\d*$")); - // //You could also set the field properties directly here if you wanted instead of the attribute - // } - // } - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + //need to figure out how to use this... + internal class RichTextPreValueEditor : PreValueEditor + { + public RichTextPreValueEditor() + { + //SD: You can add pre-val fields here like you are doing, or you can add fields using attributes (http://issues.umbraco.org/issue/U4-2692), + // see below for examples. + + //use a custom editor too + Fields.Add(new PreValueField() + { + View = "views/propertyeditors/rte/rte.prevalues.html", + HideLabel = true, + Key = "editor" + }); + + Fields.Add(new PreValueField() + { + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + + Fields.Add(new PreValueField() + { + Name = "Hide Label", + View = "boolean", + Key = "hideLabel" + }); + } + + //SD: You can declare a field like this if you want to instead of in the ctor, there's some options here: + //#1 - the property name becomes the Key: + // + // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public string Editor { get; set; } + + //#2 - You can specify a custom Key: + // + // [PreValueField("editor", "", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public string Editor { get; set; } + + //#3 - If you require custom server side validation for your field then you have to specify a custom PreValueField type to use, + // this is why in this case I find it easier to use the ctor logic but thats just an opinion + // - Any value specified for this property attribute will override the values set in on the class instance of the field, this + // allows you to re-use class instances of fields if you want. + // + // [PreValueField(typeof(EditorPreValueField))] + // public string Editor { get; set; } + + // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public class EditorPreValueField : PreValueField + // { + // public EditorPreValueField() + // { + // //add any required server validators for this field + // Validators.Add(new RegexValidator("^\\d*$")); + // //You could also set the field properties directly here if you wanted instead of the attribute + // } + // } + + } +} From 8d12170def955ee00f53dcc7d19d441ad532a395 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 14:58:58 +0200 Subject: [PATCH 083/218] Correct NuGet transform for the web.config file in the Views folder --- build/NuSpecs/tools/Views.Web.config.install.xdt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt index 4d660301a8..828bb8612f 100644 --- a/build/NuSpecs/tools/Views.Web.config.install.xdt +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -8,7 +8,7 @@ - + @@ -18,13 +18,13 @@ - + From f7aaad86c5ea29096bbce47689a0188416a99171 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 21 Jun 2019 15:56:36 +0200 Subject: [PATCH 084/218] Set focus on "Mandatory" after picking a property type when creating a new property --- .../infiniteeditors/propertysettings/propertysettings.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 93d7936326..fab6ba4069 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -91,6 +91,7 @@ From 4fca0bb735989ed7d5dd77aa566d9c48f5c70d89 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 23 Jun 2019 11:10:16 +0100 Subject: [PATCH 085/218] V8: Accessibility improvements for top header (#5544) --- .../src/less/accessibility/visually-hidden.less | 11 +++++++++++ src/Umbraco.Web.UI.Client/src/less/belle.less | 3 +++ src/Umbraco.Web.UI.Client/src/less/buttons.less | 15 +++++++++++++++ .../components/application/umb-app-header.less | 15 +++++++-------- .../components/application/umb-app-header.html | 16 ++++++++++------ 5 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/accessibility/visually-hidden.less diff --git a/src/Umbraco.Web.UI.Client/src/less/accessibility/visually-hidden.less b/src/Umbraco.Web.UI.Client/src/less/accessibility/visually-hidden.less new file mode 100644 index 0000000000..592f9dcb21 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/accessibility/visually-hidden.less @@ -0,0 +1,11 @@ + +// Visually Hidden - used to remove an element from the view, whilst retaining accessibily for screen readers. More info available at https://a11yproject.com/posts/how-to-hide-content/ +// -------------------------------------------------- + +.visually-hidden { + position: absolute !important; + height: 1px; + width: 1px; + overflow: hidden; + clip: rect(1px, 1px, 1px, 1px); +} diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 1e48500bb0..6f7554c953 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -80,6 +80,9 @@ @import "forms/umb-validation-label.less"; +// Umbraco Accessibility +@import "accessibility/visually-hidden.less"; + // Umbraco Components @import "components/application/umb-app-header.less"; @import "components/application/umb-app-content.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index f21c7f3106..91a6c29a17 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -67,6 +67,21 @@ border-color: rgba(0,0,0,0.09); } +// Button Reset - remove the default browser styles from the button element +// -------------------------------------------------- + +.btn-reset { + padding: 0; + margin: 0; + border: none; + background: none; + color: currentColor; + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + cursor: pointer; +} + // Button Sizes // -------------------------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index d4b21e66f0..bd1b8ab07a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -16,27 +16,26 @@ margin-right: -10px; } -.umb-app-header__action a { +.umb-app-header__button { padding-left: 10px; padding-right: 10px; text-decoration: none; display: flex; align-items: center; height: @appHeaderHeight; -} - -.umb-app-header__action a { outline: none; + &:focus { .tabbing-active & { .umb-app-header__action-icon::after { content: ''; position: absolute; z-index:10000; - top: -7px; - left: -7px; + top: 50%; + left: 50%; width: 36px; height: 35px; + transform: translate(-50%, -50%); border-radius: 3px; box-shadow: 0 0 2px @pinkLight, inset 0 0 2px 1px @pinkLight; } @@ -51,7 +50,7 @@ font-size: 22px; } -.umb-app-header__action a:hover .umb-app-header__action-icon, -.umb-app-header__action a:focus .umb-app-header__action-icon { +.umb-app-header__button:hover .umb-app-header__action-icon, +.umb-app-header__button:focus .umb-app-header__action-icon { opacity: 1; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index ac919d3e41..a601940193 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -1,3 +1,4 @@ +
    @@ -10,25 +11,28 @@ From 255bd10296454374489e2ac40493880f2cc9b386 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 23 Jun 2019 12:25:15 +0200 Subject: [PATCH 086/218] V8: Use a picker to select allowed types for MNTP (#5506) --- .../src/common/services/editor.service.js | 23 ++++++++ .../treepicker/treepicker.controller.js | 8 +++ .../contenttypepicker.controller.js | 54 +++++++++++++++++++ .../prevalueeditors/contenttypepicker.html | 23 ++++++++ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 1 + .../MultiNodePickerConfiguration.cs | 2 +- 8 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.html diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index a97773f77e..72957e1c72 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -368,6 +368,28 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#contentTypePicker + * @methodOf umbraco.services.editorService + * + * @description + * Opens a content type picker in infinite editing, the submit callback returns an array of selected items + * + * @param {Object} editor rendering options + * @param {Boolean} editor.multiPicker Pick one or multiple items + * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object + * @param {Function} editor.close Callback function when the close button is clicked. + * + * @returns {Object} editor object + */ + function contentTypePicker(editor) { + editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; + editor.size = "small"; + editor.section = "settings"; + editor.treeAlias = "documentTypes"; + open(editor); + } /** * @ngdoc method * @name umbraco.services.editorService#copy @@ -881,6 +903,7 @@ When building a custom infinite editor view you can use the same components as a mediaEditor: mediaEditor, contentEditor: contentEditor, contentPicker: contentPicker, + contentTypePicker: contentTypePicker, copy: copy, move: move, embed: embed, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 915abf62b0..b01fe1827b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -92,6 +92,14 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", }); } } + if (vm.treeAlias === "documentTypes") { + vm.entityType = "DocumentType"; + if (!$scope.model.title) { + localizationService.localize("defaultdialogs_selectContentType").then(function(value){ + $scope.model.title = value; + }); + } + } else if (vm.treeAlias === "member" || vm.section === "member") { vm.entityType = "Member"; if (!$scope.model.title) { diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.controller.js new file mode 100644 index 0000000000..dcb2c0e582 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.controller.js @@ -0,0 +1,54 @@ +function ContentTypePickerController($scope, contentTypeResource, editorService, angularHelper) { + var vm = this; + vm.loading = false; + vm.contentTypes = []; + vm.remove = remove; + vm.add = add; + + var allContentTypes = null; + + function init() { + vm.loading = true; + contentTypeResource.getAll().then(function (all) { + allContentTypes = all; + vm.loading = false; + // the model value is a comma separated list of content type aliases + var currentContentTypes = _.map(($scope.model.value || "").split(","), function (s) { return s.trim(); }); + vm.contentTypes = _.filter(allContentTypes, function (contentType) { + return currentContentTypes.indexOf(contentType.alias) >= 0; + }); + }); + } + + function add() { + editorService.contentTypePicker({ + multiPicker: true, + submit: function (model) { + var newContentTypes = _.map(model.selection, function (selected) { + return _.findWhere(allContentTypes, {udi: selected.udi}); + }); + vm.contentTypes = _.uniq(_.union(vm.contentTypes, newContentTypes)); + updateModel(); + editorService.close(); + }, + close: function () { + editorService.close(); + } + }); + } + + function remove(contentType) { + vm.contentTypes = _.without(vm.contentTypes, contentType); + updateModel(); + } + + function updateModel() { + // the model value is a comma separated list of content type aliases + $scope.model.value = _.pluck(vm.contentTypes, "alias").join(); + angularHelper.getCurrentForm($scope).$setDirty(); + } + + init(); +} + +angular.module('umbraco').controller("Umbraco.PrevalueEditors.ContentTypePickerController", ContentTypePickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.html new file mode 100644 index 0000000000..cd89fe4cb5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/contenttypepicker.html @@ -0,0 +1,23 @@ +
    + + + +
    + + + + + Add + +
    + +
    diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 9e6bdc5e57..0d5cdc5583 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -434,6 +434,7 @@ Vælg link Vælg makro Vælg indhold + Vælg indholdstype Vælg medie startnode Vælg medlem Vælg medlemsgruppe diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 189bd9f10b..187325b695 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -447,6 +447,7 @@ Select link Select macro Select content + Select content type Select media start node Select member Select member group diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 6ce6f82ccc..2b8b97c5f1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -451,6 +451,7 @@ Select link Select macro Select content + Select content type Select media start node Select member Select member group diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs index b6333c3140..279872e2d2 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("startNode", "Node type", "treesource")] public MultiNodePickerConfigurationTreeSource TreeSource { get; set; } - [ConfigurationField("filter", "Allow items of type", "textstring", Description = "Separate with comma")] + [ConfigurationField("filter", "Allow items of type", "contenttypepicker", Description = "Select the applicable content types")] public string Filter { get; set; } [ConfigurationField("minNumber", "Minimum number of items", "number")] From a7da3d741ffef44e0fab96a43a36b73fed44ac15 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 23 Jun 2019 12:30:17 +0200 Subject: [PATCH 087/218] V8: Add keyboard support for navigating search results (#5500) --- .../application/umbsearch.directive.js | 65 +++++++++++++++++-- .../components/application/umb-search.html | 6 +- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js index 91eb077ba3..8434a96ba5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js @@ -22,13 +22,16 @@ vm.search = search; vm.clickItem = clickItem; vm.clearSearch = clearSearch; - vm.handleKeyUp = handleKeyUp; + vm.handleKeyDown = handleKeyDown; vm.closeSearch = closeSearch; vm.focusSearch = focusSearch; //we need to capture the focus before this element is initialized. vm.focusBeforeOpening = focusService.getLastKnownFocus(); + vm.activeResult = null; + vm.activeResultGroup = null; + function onInit() { vm.searchQuery = ""; vm.searchResults = []; @@ -72,14 +75,66 @@ * Handles all keyboard events * @param {object} event */ - function handleKeyUp(event) { - - event.stopPropagation(); - event.preventDefault(); + function handleKeyDown(event) { // esc if(event.keyCode === 27) { + event.stopPropagation(); + event.preventDefault(); + closeSearch(); + return; + } + + // up/down (navigate search results) + if (vm.hasResults && (event.keyCode === 38 || event.keyCode === 40)) { + event.stopPropagation(); + event.preventDefault(); + + var allGroups = _.values(vm.searchResults); + var down = event.keyCode === 40; + if (vm.activeResultGroup === null) { + // it's the first time navigating, pick the appropriate group and result + // - first group and first result when navigating down + // - last group and last result when navigating up + vm.activeResultGroup = down ? _.first(allGroups) : _.last(allGroups); + vm.activeResult = down ? _.first(vm.activeResultGroup.results) : _.last(vm.activeResultGroup.results); + } + else if (down) { + // handle navigation down through the groups and results + if (vm.activeResult === _.last(vm.activeResultGroup.results)) { + if (vm.activeResultGroup === _.last(allGroups)) { + vm.activeResultGroup = _.first(allGroups); + } + else { + vm.activeResultGroup = allGroups[allGroups.indexOf(vm.activeResultGroup) + 1]; + } + vm.activeResult = _.first(vm.activeResultGroup.results); + } + else { + vm.activeResult = vm.activeResultGroup.results[vm.activeResultGroup.results.indexOf(vm.activeResult) + 1]; + } + } + else { + // handle navigation up through the groups and results + if (vm.activeResult === _.first(vm.activeResultGroup.results)) { + if (vm.activeResultGroup === _.first(allGroups)) { + vm.activeResultGroup = _.last(allGroups); + } + else { + vm.activeResultGroup = allGroups[allGroups.indexOf(vm.activeResultGroup) - 1]; + } + vm.activeResult = _.last(vm.activeResultGroup.results); + } + else { + vm.activeResult = vm.activeResultGroup.results[vm.activeResultGroup.results.indexOf(vm.activeResult) - 1]; + } + } + + $timeout(function () { + var resultElementLink = angular.element(".umb-search-item[active-result='true'] .umb-search-result__link"); + resultElementLink[0].focus(); + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html index 56d9eae16c..35bf725e0a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-search.html @@ -1,5 +1,5 @@ - From 917006181047aa7bc7ce8f13606f175a39c95b13 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 23 Jun 2019 12:40:23 +0200 Subject: [PATCH 088/218] V8: Add option to keep the mini profiler active at all times in debug mode (#5560) --- .../dashboard/settings/profiler.controller.js | 38 +++++++++++++++++ .../views/dashboard/settings/profiler.html | 41 +++++++++++++++++++ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 1 + .../Dashboards/ProfilerDashboard.cs | 18 ++++++++ .../Editors/BackOfficeServerVariables.cs | 5 +++ src/Umbraco.Web/Logging/WebProfiler.cs | 1 + .../Profiling/WebProfilingController.cs | 19 +++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + src/Umbraco.Web/UmbracoContext.cs | 3 +- 11 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html create mode 100644 src/Umbraco.Web/Dashboards/ProfilerDashboard.cs create mode 100644 src/Umbraco.Web/Profiling/WebProfilingController.cs diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.controller.js new file mode 100644 index 0000000000..295263a47c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.controller.js @@ -0,0 +1,38 @@ +function ProfilerController($scope, $cookies, $http, umbRequestHelper) { + var vm = this; + + vm.loading = true; + vm.toggle = toggle; + + function toggle() { + if (vm.alwaysOn === true) { + $cookies.remove("UMB-DEBUG", { + path: "/" + }); + vm.alwaysOn = false; + } + else { + $cookies.put("UMB-DEBUG", "true", { + path: "/", + expires: "Tue, 01 Jan 2100 00:00:01 GMT" + }); + vm.alwaysOn = true; + } + } + + function init() { + vm.alwaysOn = $cookies.get("UMB-DEBUG") === "true"; + + umbRequestHelper.resourcePromise( + $http.get(umbRequestHelper.getApiUrl("webProfilingBaseUrl", "GetStatus")), + "Failed to retrieve status for web profiling" + ).then(function(status) { + vm.loading = false; + vm.profilerEnabled = status.Enabled; + }); + } + + init(); +} + +angular.module("umbraco").controller("Umbraco.Dashboard.ProfilerController", ProfilerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html new file mode 100644 index 0000000000..2a7419c0ea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html @@ -0,0 +1,41 @@ +
    + + +

    Performance profiling

    +
    +

    + Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. +

    +

    + If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. +

    +

    + If you want the profiler to be activated by default for all page renderings, you can use the toggle below. + It will set a cookie in your browser, which then activates the profiler automatically. + In other words, the profiler will only be active by default in your browser - not everyone else's. +

    +

     

    +
    +
    +
    Activate the profiler by default
    +
    +
    + +
    +
    +

    Friendly reminder

    +

    + You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. +

    +
    +
    +

    + Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. +

    +

    + Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. +

    +
    +
    +
    +
    diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 0d5cdc5583..aac2f6ddd5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1606,6 +1606,7 @@ Mange hilsner fra Umbraco robotten Published Cache Models Builder Health Check + Profiling Kom godt i gang Installer Umbraco Forms diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 187325b695..aba67ff2b1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2117,6 +2117,7 @@ To manage your website, simply open the Umbraco back office and start adding con Published Status Models Builder Health Check + Profiling Getting Started Install Umbraco Forms > diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 2b8b97c5f1..6b9392ff3e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2132,6 +2132,7 @@ To manage your website, simply open the Umbraco back office and start adding con Published Status Models Builder Health Check + Profiling Getting Started Install Umbraco Forms diff --git a/src/Umbraco.Web/Dashboards/ProfilerDashboard.cs b/src/Umbraco.Web/Dashboards/ProfilerDashboard.cs new file mode 100644 index 0000000000..a4f51398e8 --- /dev/null +++ b/src/Umbraco.Web/Dashboards/ProfilerDashboard.cs @@ -0,0 +1,18 @@ +using System; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards +{ + [Weight(60)] + public class ProfilerDashboardDashboard : IDashboard + { + public string Alias => "settingsProfiler"; + + public string[] Sections => new [] { "settings" }; + + public string View => "views/dashboard/settings/profiler.html"; + + public IAccessRule[] AccessRules => Array.Empty(); + } +} diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 4966328782..6b6cfacacc 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -20,6 +20,7 @@ using Umbraco.Web.Features; using Umbraco.Web.HealthCheck; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.Profiling; using Umbraco.Web.PropertyEditors; using Umbraco.Web.Trees; using Constants = Umbraco.Core.Constants; @@ -308,6 +309,10 @@ namespace Umbraco.Web.Editors { "logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetNumberOfErrors(null, null)) + }, + { + "webProfilingBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetStatus()) } } }, diff --git a/src/Umbraco.Web/Logging/WebProfiler.cs b/src/Umbraco.Web/Logging/WebProfiler.cs index 14c1bb065f..512edb2296 100755 --- a/src/Umbraco.Web/Logging/WebProfiler.cs +++ b/src/Umbraco.Web/Logging/WebProfiler.cs @@ -68,6 +68,7 @@ namespace Umbraco.Web.Logging if (request.Result.Url.IsClientSideRequest()) return false; if (bool.TryParse(request.Result.QueryString["umbDebug"], out var umbDebug)) return umbDebug; if (bool.TryParse(request.Result.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug; + if (bool.TryParse(request.Result.Cookies["UMB-DEBUG"]?.Value, out var cUmbDebug)) return cUmbDebug; return false; } diff --git a/src/Umbraco.Web/Profiling/WebProfilingController.cs b/src/Umbraco.Web/Profiling/WebProfilingController.cs new file mode 100644 index 0000000000..b3d580bc38 --- /dev/null +++ b/src/Umbraco.Web/Profiling/WebProfilingController.cs @@ -0,0 +1,19 @@ +using Umbraco.Web.Editors; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Profiling +{ + /// + /// The API controller used to display the state of the web profiler + /// + [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] + public class WebProfilingController : UmbracoAuthorizedJsonController + { + public object GetStatus() + { + return new + { + Enabled = Core.Configuration.GlobalSettings.DebugMode + }; + } + }} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e565f354c8..7ebcf55155 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -129,6 +129,7 @@ + @@ -211,6 +212,7 @@ + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index e68e6e2c77..a5ff08d79c 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -174,7 +174,8 @@ namespace Umbraco.Web return GlobalSettings.DebugMode && request != null && (string.IsNullOrEmpty(request["umbdebugshowtrace"]) == false - || string.IsNullOrEmpty(request["umbdebug"]) == false); + || string.IsNullOrEmpty(request["umbdebug"]) == false + || string.IsNullOrEmpty(request.Cookies["UMB-DEBUG"]?.Value) == false); } } From 9fffdb407a3b73d9d9ecf384ffdca144fdb3bb80 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 2 Jun 2019 09:51:32 +0200 Subject: [PATCH 089/218] Provided alternative messages for users that do and do not have access to document/media types for when trying to create content or media and no types can be created. Where they do, provided a link to go to the page where child nodes can be edited. --- .../content/content.create.controller.js | 159 ++++++++++-------- .../src/views/content/create.html | 14 +- .../views/documenttypes/edit.controller.js | 27 ++- .../src/views/media/create.html | 14 +- .../views/media/media.create.controller.js | 17 +- .../src/views/mediatypes/edit.controller.js | 30 +++- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 8 +- .../Umbraco/config/lang/en_us.xml | 9 +- .../Models/ContentEditing/ContentItemBasic.cs | 2 + .../ContentEditing/ContentItemDisplay.cs | 3 + .../Models/Mapping/ContentMapDefinition.cs | 2 + .../Models/Mapping/MediaMapDefinition.cs | 2 + .../Models/Mapping/MemberMapDefinition.cs | 4 +- 13 files changed, 198 insertions(+), 93 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index f101450705..d04c707b25 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -7,86 +7,103 @@ * The controller for the content creation dialog */ function contentCreateController($scope, - $routeParams, - contentTypeResource, - iconHelper, - $location, - navigationService, - blueprintConfig) { - - var mainCulture = $routeParams.mculture ? $routeParams.mculture : null; + $routeParams, + contentTypeResource, + iconHelper, + $location, + navigationService, + blueprintConfig, + authResource, + contentResource) { - function initialize() { - $scope.allowedTypes = null; - contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { - $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); - }); + var mainCulture = $routeParams.mculture ? $routeParams.mculture : null; - $scope.selectContentType = true; - $scope.selectBlueprint = false; - $scope.allowBlank = blueprintConfig.allowBlank; - } + function initialize() { + $scope.allowedTypes = null; + contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { + $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + }); - function close() { - navigationService.hideMenu(); - } + if ($scope.currentNode.id > -1) { + authResource.getCurrentUser().then(function(currentUser) { + if (currentUser.allowedSections.indexOf("settings") > -1) { + $scope.hasSettingsAccess = true; + contentResource.getById($scope.currentNode.id).then(function(data) { + $scope.contentTypeId = data.contentTypeId; + }); + } + }); + } - function createBlank(docType) { - $location - .path("/content/content/edit/" + $scope.currentNode.id) - .search("doctype", docType.alias) - .search("create", "true") - /* when we create a new node we want to make sure it uses the same - language as what is selected in the tree */ - .search("cculture", mainCulture); - close(); - } - - function createOrSelectBlueprintIfAny(docType) { - // map the blueprints into a collection that's sortable in the view - var blueprints = _.map(_.pairs(docType.blueprints || {}), function (pair) { - return { - id: pair[0], - name: pair[1] - }; - }); - $scope.docType = docType; - if (blueprints.length) { - if (blueprintConfig.skipSelect) { - createFromBlueprint(blueprints[0].id); - } else { - $scope.selectContentType = false; - $scope.selectBlueprint = true; - $scope.selectableBlueprints = blueprints; - } - } else { - createBlank(docType); + $scope.selectContentType = true; + $scope.selectBlueprint = false; + $scope.allowBlank = blueprintConfig.allowBlank; } - } - function createFromBlueprint(blueprintId) { - $location - .path("/content/content/edit/" + $scope.currentNode.id) - .search("doctype", $scope.docType.alias) - .search("create", "true") - .search("blueprintId", blueprintId); - close(); - } + function close() { + navigationService.hideMenu(); + } - $scope.closeDialog = function(showMenu) { - navigationService.hideDialog(showMenu); - }; + function createBlank(docType) { + $location + .path("/content/content/edit/" + $scope.currentNode.id) + .search("doctype", docType.alias) + .search("create", "true") + /* when we create a new node we want to make sure it uses the same + language as what is selected in the tree */ + .search("cculture", mainCulture); + close(); + } - $scope.createBlank = createBlank; - $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; - $scope.createFromBlueprint = createFromBlueprint; + function createOrSelectBlueprintIfAny(docType) { + // map the blueprints into a collection that's sortable in the view + var blueprints = _.map(_.pairs(docType.blueprints || {}), function (pair) { + return { + id: pair[0], + name: pair[1] + }; + }); + $scope.docType = docType; + if (blueprints.length) { + if (blueprintConfig.skipSelect) { + createFromBlueprint(blueprints[0].id); + } else { + $scope.selectContentType = false; + $scope.selectBlueprint = true; + $scope.selectableBlueprints = blueprints; + } + } else { + createBlank(docType); + } + } - // the current node changes behind the scenes when the context menu is clicked without closing - // the default menu first, so we must watch the current node and re-initialize accordingly - var unbindModelWatcher = $scope.$watch("currentNode", initialize); - $scope.$on('$destroy', function () { - unbindModelWatcher(); - }); + function createFromBlueprint(blueprintId) { + $location + .path("/content/content/edit/" + $scope.currentNode.id) + .search("doctype", $scope.docType.alias) + .search("create", "true") + .search("blueprintId", blueprintId); + close(); + } + + $scope.close = function() { + close(); + } + + $scope.closeDialog = function (showMenu) { + navigationService.hideDialog(showMenu); + }; + + $scope.createBlank = createBlank; + $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; + $scope.createFromBlueprint = createFromBlueprint; + + // the current node changes behind the scenes when the context menu is clicked without closing + // the default menu first, so we must watch the current node and re-initialize accordingly + var unbindModelWatcher = $scope.$watch("currentNode", initialize); + $scope.$on('$destroy', function () { + unbindModelWatcher(); + }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index 94299f6a54..97306e0ea8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -6,9 +6,15 @@
    Create a page under {{currentNode.name}}
    Select a blueprint
    -

    - -

    +
      @@ -56,4 +62,4 @@
    -
    \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 7c1f996931..5ceb5f01f2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -56,7 +56,7 @@ function onInit() { // get init values from model when in infinite mode - if(infiniteMode) { + if (infiniteMode) { documentTypeId = $scope.model.id; create = $scope.model.create; noTemplate = $scope.model.notemplate; @@ -89,8 +89,7 @@ "name": vm.labels.design, "alias": "design", "icon": "icon-document-dashed-line", - "view": "views/documenttypes/views/design/design.html", - "active": true + "view": "views/documenttypes/views/design/design.html" }, { "name": vm.labels.listview, @@ -291,6 +290,28 @@ }); vm.page.navigation = buttons; + initializeActiveNavigationPanel(); + } + + function initializeActiveNavigationPanel() { + // Initialise first loaded panel based on page route paramater + // i.e. ?view=design|listview|permissions + var initialViewSetFromRouteParams = false; + var view = $routeParams.view; + if (view) { + var viewPath = "views/documenttypes/views/" + view + "/" + view + ".html"; + for (var i = 0; i < vm.page.navigation.length; i++) { + if (vm.page.navigation[i].view === viewPath) { + vm.page.navigation[i].active = true; + initialViewSetFromRouteParams = true; + break; + } + } + } + + if (initialViewSetFromRouteParams === false) { + vm.page.navigation[0].active = true; + } } /* ---------- SAVE ---------- */ diff --git a/src/Umbraco.Web.UI.Client/src/views/media/create.html b/src/Umbraco.Web.UI.Client/src/views/media/create.html index 13c12f3c9a..d93d4f0e30 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/create.html @@ -4,9 +4,15 @@
    Create under {{currentNode.name}}
    -

    - -

    +
    +

    +
    +

    + + + +
    +