diff --git a/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs index 0ae721943d..14fef80f0d 100644 --- a/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs @@ -26,6 +26,9 @@ namespace Umbraco.Core.Cache public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider) { + if (innerProvider.GetType() == typeof(DeepCloneRuntimeCacheProvider)) + throw new InvalidOperationException("A " + typeof(DeepCloneRuntimeCacheProvider) + " cannot wrap another instance of " + typeof(DeepCloneRuntimeCacheProvider)); + InnerProvider = innerProvider; } @@ -105,9 +108,11 @@ namespace Umbraco.Core.Cache var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache if (value == null) return null; // do not store null values (backward compat) + //Clone/reset to go into the cache return CheckCloneableAndTracksChanges(value); }, timeout, isSliding, priority, removedCallback, dependentFiles); + //Clone/reset to go out of the cache return CheckCloneableAndTracksChanges(cached); } diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index ec73e1ff5e..cef06ec4f7 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -274,6 +274,9 @@ namespace Umbraco.Core.Models /// public bool HasPublishedVersion { get { return PublishedVersionGuid != default(Guid); } } + [IgnoreDataMember] + internal DateTime PublishedDate { get; set; } + /// /// Changes the Trashed state of the content object /// diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs index 4a7e359d91..7c6507f499 100644 --- a/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs @@ -19,5 +19,8 @@ namespace Umbraco.Core.Models.Rdbms [Column("newest")] public bool Newest { get; set; } + + [Column("updateDate")] + public DateTime VersionDate { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs index 5e170f47f4..ad35b81ffb 100644 --- a/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs +++ b/src/Umbraco.Core/ObjectResolution/ManyObjectsResolverBase.cs @@ -22,6 +22,7 @@ namespace Umbraco.Core.ObjectResolution private readonly string _httpContextKey; private readonly List _instanceTypes = new List(); private IEnumerable _sortedValues; + private readonly Func _httpContextGetter; private int _defaultPluginWeight = 100; @@ -42,12 +43,7 @@ namespace Umbraco.Core.ObjectResolution if (logger == null) throw new ArgumentNullException("logger"); CanResolveBeforeFrozen = false; if (scope == ObjectLifetimeScope.HttpRequest) - { - if (HttpContext.Current == null) - throw new InvalidOperationException("Use alternative constructor accepting a HttpContextBase object in order to set the lifetime scope to HttpRequest when HttpContext.Current is null"); - - CurrentHttpContext = new HttpContextWrapper(HttpContext.Current); - } + _httpContextGetter = () => new HttpContextWrapper(HttpContext.Current); ServiceProvider = serviceProvider; Logger = logger; @@ -84,7 +80,7 @@ namespace Umbraco.Core.ObjectResolution LifetimeScope = ObjectLifetimeScope.HttpRequest; _httpContextKey = GetType().FullName; ServiceProvider = serviceProvider; - CurrentHttpContext = httpContext; + _httpContextGetter = () => httpContext; _instanceTypes = new List(); InitializeAppInstances(); @@ -160,7 +156,16 @@ namespace Umbraco.Core.ObjectResolution /// Gets or sets the used to initialize this object, if any. /// /// If not null, then LifetimeScope will be ObjectLifetimeScope.HttpRequest. - protected HttpContextBase CurrentHttpContext { get; private set; } + protected HttpContextBase CurrentHttpContext + { + get + { + var context = _httpContextGetter == null ? null : _httpContextGetter(); + if (context == null) + throw new InvalidOperationException("Cannot use this resolver with lifetime 'HttpRequest' when there is no current HttpContext. Either use the ctor accepting an HttpContextBase, or use the resolver from within a request exclusively."); + return context; + } + } /// /// Returns the service provider used to instantiate objects @@ -196,7 +201,7 @@ namespace Umbraco.Core.ObjectResolution /// /// Gets or sets the default type weight. /// - /// Determines the weight of types that do not have a WeightAttribute set on + /// Determines the weight of types that do not have a WeightAttribute set on /// them, when calling GetSortedValues. protected virtual int DefaultPluginWeight { @@ -276,7 +281,7 @@ namespace Umbraco.Core.ObjectResolution /// Removes a type. /// /// The type to remove. - /// the resolver does not support removing types, or + /// the resolver does not support removing types, or /// the type is not a valid type for the resolver. public virtual void RemoveType(Type value) { @@ -296,7 +301,7 @@ namespace Umbraco.Core.ObjectResolution /// Removes a type. /// /// The type to remove. - /// the resolver does not support removing types, or + /// the resolver does not support removing types, or /// the type is not a valid type for the resolver. public void RemoveType() where T : TResolved @@ -309,7 +314,7 @@ namespace Umbraco.Core.ObjectResolution /// /// The types to add. /// The types are appended at the end of the list. - /// the resolver does not support adding types, or + /// the resolver does not support adding types, or /// a type is not a valid type for the resolver, or a type is already in the collection of types. protected void AddTypes(IEnumerable types) { @@ -336,7 +341,7 @@ namespace Umbraco.Core.ObjectResolution /// /// The type to add. /// The type is appended at the end of the list. - /// the resolver does not support adding types, or + /// the resolver does not support adding types, or /// the type is not a valid type for the resolver, or the type is already in the collection of types. public virtual void AddType(Type value) { @@ -362,7 +367,7 @@ namespace Umbraco.Core.ObjectResolution /// /// The type to add. /// The type is appended at the end of the list. - /// the resolver does not support adding types, or + /// the resolver does not support adding types, or /// the type is not a valid type for the resolver, or the type is already in the collection of types. public void AddType() where T : TResolved @@ -404,7 +409,7 @@ namespace Umbraco.Core.ObjectResolution /// /// The zero-based index at which the type should be inserted. /// The type to insert. - /// the resolver does not support inserting types, or + /// the resolver does not support inserting types, or /// the type is not a valid type for the resolver, or the type is already in the collection of types. /// is out of range. public virtual void InsertType(int index, Type value) @@ -430,7 +435,7 @@ namespace Umbraco.Core.ObjectResolution /// Inserts a type at the beginning of the list. /// /// The type to insert. - /// the resolver does not support inserting types, or + /// the resolver does not support inserting types, or /// the type is not a valid type for the resolver, or the type is already in the collection of types. public virtual void InsertType(Type value) { @@ -464,7 +469,7 @@ namespace Umbraco.Core.ObjectResolution /// /// The existing type before which to insert. /// The type to insert. - /// the resolver does not support inserting types, or + /// the resolver does not support inserting types, or /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, /// or the new type is already in the collection of types. public virtual void InsertTypeBefore(Type existingType, Type value) @@ -498,7 +503,7 @@ namespace Umbraco.Core.ObjectResolution /// /// The existing type before which to insert. /// The type to insert. - /// the resolver does not support inserting types, or + /// the resolver does not support inserting types, or /// one of the types is not a valid type for the resolver, or the existing type is not in the collection, /// or the new type is already in the collection of types. public void InsertTypeBefore() diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index 0532eab6b1..512f02e8b5 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -72,6 +72,9 @@ namespace Umbraco.Core.Persistence.Factories content.PublishedVersionGuid = publishedDto == null ? (dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId) : publishedDto.VersionId; + content.PublishedDate = publishedDto == null + ? (dto.DocumentPublishedReadOnlyDto == null ? default(DateTime) : dto.DocumentPublishedReadOnlyDto.VersionDate) + : publishedDto.VersionDate; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index ae656b3f5b..a34a77d3b4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -513,11 +513,13 @@ namespace Umbraco.Core.Persistence.Repositories dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto { VersionId = dto.VersionId, + VersionDate = dto.UpdateDate, Newest = true, NodeId = dto.NodeId, - Published = true + Published = true }; - ((Content)entity).PublishedVersionGuid = dto.VersionId; + ((Content) entity).PublishedVersionGuid = dto.VersionId; + ((Content) entity).PublishedDate = dto.UpdateDate; } entity.ResetDirtyProperties(); @@ -688,22 +690,26 @@ namespace Umbraco.Core.Persistence.Repositories dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto { VersionId = dto.VersionId, + VersionDate = dto.UpdateDate, Newest = true, NodeId = dto.NodeId, Published = true }; - ((Content)entity).PublishedVersionGuid = dto.VersionId; + ((Content) entity).PublishedVersionGuid = dto.VersionId; + ((Content) entity).PublishedDate = dto.UpdateDate; } else if (publishedStateChanged) { dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto { - VersionId = default(Guid), + VersionId = default (Guid), + VersionDate = default (DateTime), Newest = false, NodeId = dto.NodeId, Published = false }; - ((Content)entity).PublishedVersionGuid = default(Guid); + ((Content) entity).PublishedVersionGuid = default(Guid); + ((Content) entity).PublishedDate = default (DateTime); } entity.ResetDirtyProperties(); @@ -975,7 +981,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", } //order by update date DESC, if there is corrupted published flags we only want the latest! - var publishedSql = new Sql(@"SELECT cmsDocument.nodeId, cmsDocument.published, cmsDocument.versionId, cmsDocument.newest + var publishedSql = new Sql(@"SELECT cmsDocument.nodeId, cmsDocument.published, cmsDocument.versionId, cmsDocument.updateDate, cmsDocument.newest FROM cmsDocument INNER JOIN cmsContentVersion ON cmsContentVersion.VersionId = cmsDocument.versionId WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN (" + parsedOriginalSql + @") diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index c3387b1faf..13081cb2a4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -1252,5 +1252,19 @@ WHERE cmsContentType." + aliasColumn + @" LIKE @pattern", while (aliases.Contains(test = alias + i)) i++; return test; } + + /// + /// Given the path of a content item, this will return true if the content item exists underneath a list view content item + /// + /// + /// + public bool HasContainerInPath(string contentPath) + { + var ids = contentPath.Split(',').Select(int.Parse); + var sql = new Sql(@"SELECT COUNT(*) FROM cmsContentType +INNER JOIN cmsContent ON cmsContentType.nodeId=cmsContent.contentType +WHERE cmsContent.nodeId IN (@ids) AND cmsContentType.isContainer=@isContainer", new { ids, isContainer = true }); + return Database.ExecuteScalar(sql) > 0; + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 071bf7eba8..e3da00b410 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -276,47 +276,33 @@ AND umbracoNode.id <> @id", public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) { - var cached = IsolatedCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); - if (cached != null && cached.Any()) - { - //return from the cache, ensure it's a cloned result - return (PreValueCollection)cached.First().DeepClone(); - } - - return GetAndCachePreValueCollection(dataTypeId); - } - - internal static string GetCacheKeyRegex(int preValueId) - { - return CacheKeys.DataTypePreValuesCacheKey + @"[-\d]+-([\d]*,)*" + preValueId + @"(?!\d)[,\d$]*"; + var collection = GetCachedPreValueCollection(dataTypeId); + return collection; } + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string public string GetPreValueAsString(int preValueId) { - //We need to see if we can find the cached PreValueCollection based on the cache key above + var collections = RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + "_"); - var cached = IsolatedCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId)); - if (cached != null && cached.Any()) - { - //return from the cache - var collection = cached.First(); - var preVal = collection.FormatAsDictionary().Single(x => x.Value.Id == preValueId); - return preVal.Value.Value; - } + var preValue = collections.SelectMany(x => x.FormatAsDictionary().Values).FirstOrDefault(x => x.Id == preValueId); + if (preValue != null) + return preValue.Value; - //go and find the data type id for the pre val id passed in - - var dto = Database.FirstOrDefault("WHERE id = @preValueId", new { preValueId = preValueId }); + var dto = Database.FirstOrDefault("WHERE id = @preValueId", new { preValueId }); if (dto == null) - { return string.Empty; - } - // go cache the collection - var preVals = GetAndCachePreValueCollection(dto.DataTypeNodeId); - //return the single value for this id - var pv = preVals.FormatAsDictionary().Single(x => x.Value.Id == preValueId); - return pv.Value.Value; + var collection = GetCachedPreValueCollection(dto.DataTypeNodeId); + if (collection == null) + return string.Empty; + + preValue = collection.FormatAsDictionary().Values.FirstOrDefault(x => x.Id == preValueId); + return preValue == null ? string.Empty : preValue.Value; } public void AddOrUpdatePreValues(int dataTypeId, IDictionary values) @@ -441,40 +427,28 @@ AND umbracoNode.id <> @id", sortOrder++; } - } - private string GetPrefixedCacheKey(int dataTypeId) + private static string GetPrefixedCacheKey(int dataTypeId) { - return CacheKeys.DataTypePreValuesCacheKey + dataTypeId + "-"; + return CacheKeys.DataTypePreValuesCacheKey + "_" + dataTypeId; } - private PreValueCollection GetAndCachePreValueCollection(int dataTypeId) + private PreValueCollection GetCachedPreValueCollection(int datetypeId) { - //go get the data - var dtos = Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = dataTypeId }); - var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value, x.SortOrder), x.Alias, x.SortOrder)).ToList(); - var collection = PreValueConverter.ConvertToPreValuesCollection(list); - - //now create the cache key, this needs to include all pre-value ids so that we can use this cached item in the GetPreValuesAsString method - //the key will be: "UmbracoPreValDATATYPEID-CSVOFPREVALIDS - - var key = GetPrefixedCacheKey(dataTypeId) - + string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray()); - - //store into cache - IsolatedCache.InsertCacheItem(key, () => collection, - //30 mins - new TimeSpan(0, 0, 30), - //sliding is true - true); - - return collection; + var key = GetPrefixedCacheKey(datetypeId); + return RuntimeCache.GetCacheItem(key, () => + { + var dtos = Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = datetypeId }); + var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value, x.SortOrder), x.Alias, x.SortOrder)).ToList(); + var collection = PreValueConverter.ConvertToPreValuesCollection(list); + return collection; + }, TimeSpan.FromMinutes(20), isSliding: true); } private string EnsureUniqueNodeName(string nodeName, int id = 0) { - + var sql = new Sql(); sql.Select("*") @@ -575,7 +549,7 @@ AND umbracoNode.id <> @id", } //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT - // in reality there is no need to check the uniqueness of this alias because the only way that this code executes is + // in reality there is no need to check the uniqueness of this alias because the only way that this code executes is // based on an IDictionary dictionary being passed to this repository and a dictionary // must have unique aliases by definition, so there is no need for this additional check @@ -595,10 +569,10 @@ AND umbracoNode.id <> @id", { throw new InvalidOperationException("Cannot update a pre value for a data type that has no identity"); } - + //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT // this causes issues when sorting the pre-values (http://issues.umbraco.org/issue/U4-5670) but in reality - // there is no need to check the uniqueness of this alias because the only way that this code executes is + // there is no need to check the uniqueness of this alias because the only way that this code executes is // based on an IDictionary dictionary being passed to this repository and a dictionary // must have unique aliases by definition, so there is no need for this additional check @@ -613,7 +587,7 @@ AND umbracoNode.id <> @id", Database.Update(dto); } - + } internal static class PreValueConverter diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index 61d83645b3..f118be3b76 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -8,6 +8,13 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IContentTypeRepository : IContentTypeCompositionRepository { + /// + /// Given the path of a content item, this will return true if the content item exists underneath a list view content item + /// + /// + /// + bool HasContainerInPath(string contentPath); + /// /// Gets all entities of the specified query /// diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 8ba5e5727f..afb93f620f 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -49,7 +49,12 @@ namespace Umbraco.Core.Persistence _cacheHelper.IsolatedRuntimeCache.CacheFactory = type => { var cache = origFactory(type); - return new DeepCloneRuntimeCacheProvider(cache); + + //if the result is already a DeepCloneRuntimeCacheProvider then return it, otherwise + //wrap the result with a DeepCloneRuntimeCacheProvider + return cache is DeepCloneRuntimeCacheProvider + ? cache + : new DeepCloneRuntimeCacheProvider(cache); }; } diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs index 28bb58fb5a..c2dfd687dd 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services @@ -74,5 +75,20 @@ namespace Umbraco.Core.Services { return Enumerable.Empty(); } + + /// + /// Given the path of a content item, this will return true if the content item exists underneath a list view content item + /// + /// + /// + public bool HasContainerInPath(string contentPath) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + // can use same repo for both content and media + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + return repository.HasContainerInPath(contentPath); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 2dcdf01291..46ee8520c6 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -11,6 +11,13 @@ namespace Umbraco.Core.Services /// public interface IContentTypeService : IService { + /// + /// Given the path of a content item, this will return true if the content item exists underneath a list view content item + /// + /// + /// + bool HasContainerInPath(string contentPath); + int CountContentTypes(); int CountMediaTypes(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 4f1569ac7b..fc65c51e22 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -46,23 +46,6 @@ namespace Umbraco.Tests.Persistence.Repositories return new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, Constants.ObjectTypes.DataTypeContainerGuid); } - [TestCase("UmbracoPreVal87-21,3,48", 3, true)] - [TestCase("UmbracoPreVal87-21,33,48", 3, false)] - [TestCase("UmbracoPreVal87-21,33,48", 33, true)] - [TestCase("UmbracoPreVal87-21,3,48", 33, false)] - [TestCase("UmbracoPreVal87-21,3,48", 21, true)] - [TestCase("UmbracoPreVal87-21,3,48", 48, true)] - [TestCase("UmbracoPreVal87-22,33,48", 2, false)] - [TestCase("UmbracoPreVal87-22,33,48", 22, true)] - [TestCase("UmbracoPreVal87-22,33,44", 4, false)] - [TestCase("UmbracoPreVal87-22,33,44", 44, true)] - [TestCase("UmbracoPreVal87-22,333,44", 33, false)] - [TestCase("UmbracoPreVal87-22,333,44", 333, true)] - public void Pre_Value_Cache_Key_Tests(string cacheKey, int preValueId, bool outcome) - { - Assert.AreEqual(outcome, Regex.IsMatch(cacheKey, DataTypeDefinitionRepository.GetCacheKeyRegex(preValueId))); - } - [Test] public void Can_Move() { @@ -537,7 +520,7 @@ namespace Umbraco.Tests.Persistence.Repositories } var cached = cache.IsolatedRuntimeCache.GetCache().Result - .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + "_" + dtd.Id); Assert.IsNotNull(cached); Assert.AreEqual(1, cached.Count()); @@ -581,7 +564,7 @@ namespace Umbraco.Tests.Persistence.Repositories } var cached = cache.IsolatedRuntimeCache.GetCache().Result - .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + "_" + dtd.Id); Assert.IsNotNull(cached); Assert.AreEqual(1, cached.Count()); diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 34f5ecd282..2b7a6f7043 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -993,7 +993,7 @@ To manage your website, simply open the Umbraco back office and start adding con Dictionary item saved Publishing failed because the parent page isn't published Content published - and visible at the website + and visible on the website Content saved Remember to publish to make changes visible Sent For Approval 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 7139d0e398..ce9618f679 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -982,7 +982,7 @@ To manage your website, simply open the Umbraco back office and start adding con Dictionary item saved Publishing failed because the parent page isn't published Content published - and visible at the website + and visible on the website Content saved Remember to publish to make changes visible Sent For Approval diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index fe629a6ccf..a5cc8d3806 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -103,13 +103,13 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey); + var dataTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); payloads.ForEach(payload => { //clears the prevalue cache - var dataTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); if (dataTypeCache) - dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); - + dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}_{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); + PublishedContentType.ClearDataType(payload.Id); }); diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs index bdfd504d73..54f72dc88a 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs @@ -64,7 +64,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security var url = HealthCheckContext.HttpContext.Request.Url; // Access the site home page and check for the click-jack protection header or meta tag - var useSsl = GlobalSettings.UseSSL || HealthCheckContext.HttpContext.Request.ServerVariables["SERVER_PORT"] == "443"; + var serverVariables = HealthCheckContext.HttpContext.Request.ServerVariables; + var useSsl = GlobalSettings.UseSSL || serverVariables["SERVER_PORT"] == "443"; var address = string.Format("http{0}://{1}:{2}", useSsl ? "s" : "", url.Host.ToLower(), url.Port); var request = WebRequest.Create(address); request.Method = "GET"; diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs index cf279bf3f8..2ca63662d8 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs @@ -49,7 +49,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security var url = HealthCheckContext.HttpContext.Request.Url; // Access the site home page and check for the headers - var useSsl = GlobalSettings.UseSSL || HealthCheckContext.HttpContext.Request.ServerVariables["SERVER_PORT"] == "443"; + var serverVariables = HealthCheckContext.HttpContext.Request.ServerVariables; + var useSsl = GlobalSettings.UseSSL || serverVariables["SERVER_PORT"] == "443"; var address = string.Format("http{0}://{1}:{2}", useSsl ? "s" : "", url.Host.ToLower(), url.Port); var request = WebRequest.Create(address); request.Method = "HEAD"; diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckController.cs b/src/Umbraco.Web/HealthCheck/HealthCheckController.cs index 919df88962..14bfaaea9f 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckController.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckController.cs @@ -48,12 +48,22 @@ namespace Umbraco.Web.HealthCheck return healthCheckGroups; } + [HttpGet] public object GetStatus(Guid id) { var check = _healthCheckResolver.HealthChecks.FirstOrDefault(x => x.Id == id); if (check == null) throw new InvalidOperationException("No health check found with ID " + id); - return check.GetStatus(); + try + { + //Core.Logging.LogHelper.Debug("Running health check: " + check.Name); + return check.GetStatus(); + } + catch (Exception e) + { + Core.Logging.LogHelper.Error("Exception in health check: " + check.Name, e); + throw; + } } [HttpPost] diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs index dfe5b792a5..7ae302fa49 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.HealthCheck /// internal class HealthCheckResolver : LazyManyObjectsResolverBase, IHealthCheckResolver { - public HealthCheckResolver(ILogger logger, Func> lazyTypeList) + public HealthCheckResolver(ILogger logger, Func> lazyTypeList) : base(new HealthCheckServiceProvider(), logger, lazyTypeList, ObjectLifetimeScope.HttpRequest) { } @@ -51,7 +51,7 @@ namespace Umbraco.Web.HealthCheck new HealthCheckContext(new HttpContextWrapper(HttpContext.Current), UmbracoContext.Current) }); } - + //use normal ctor return Activator.CreateInstance(serviceType); } diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 6f23ecee0f..37b002d297 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -37,7 +37,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.IsContainer, expression => expression.MapFrom(content => content.ContentType.IsContainer)) .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) .ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed)) - .ForMember(display => display.PublishDate, expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext))) + .ForMember(display => display.PublishDate, expression => expression.MapFrom(content => GetPublishedDate(content))) .ForMember(display => display.TemplateAlias, expression => expression.MapFrom(content => content.Template.Alias)) .ForMember(display => display.HasPublishedVersion, expression => expression.MapFrom(content => content.HasPublishedVersion)) .ForMember(display => display.Urls, @@ -78,6 +78,12 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.Alias, expression => expression.Ignore()); } + private static DateTime? GetPublishedDate(IContent content) + { + var date = ((Content) content).PublishedDate; + return date == default (DateTime) ? (DateTime?) null : date; + } + /// /// Maps the generic tab with custom properties for content /// @@ -89,31 +95,9 @@ namespace Umbraco.Web.Models.Mapping private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService) { - //map the IsChildOfListView (this is actually if it is a descendant of a list view!) - //TODO: Fix this shorthand .Ancestors() lookup, at least have an overload to use the current - if (content.HasIdentity) - { - var ancesctorListView = content.Ancestors().FirstOrDefault(x => x.ContentType.IsContainer); - display.IsChildOfListView = ancesctorListView != null; - } - else - { - //it's new so it doesn't have a path, so we need to look this up by it's parent + ancestors - var parent = content.Parent(); - if (parent == null) - { - display.IsChildOfListView = false; - } - else if (parent.ContentType.IsContainer) - { - display.IsChildOfListView = true; - } - else - { - var ancesctorListView = parent.Ancestors().FirstOrDefault(x => x.ContentType.IsContainer); - display.IsChildOfListView = ancesctorListView != null; - } - } + // map the IsChildOfListView (this is actually if it is a descendant of a list view!) + var parent = content.Parent(); + display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); //map the tree node url if (HttpContext.Current != null) @@ -229,26 +213,7 @@ namespace Umbraco.Web.Models.Mapping } /// - /// Gets the published date value for the IContent object - /// - /// - /// - /// - private static DateTime? GetPublishedDate(IContent content, ApplicationContext applicationContext) - { - if (content.Published) - { - return content.UpdateDate; - } - if (content.HasPublishedVersion) - { - var published = applicationContext.Services.ContentService.GetPublishedVersion(content.Id); - return published.UpdateDate; - } - return null; - } - - /// + //TODO: This is horribly inneficient /// Creates the list of action buttons allowed for this user - Publish, Send to publish, save, unpublish returned as the button's 'letter' /// private class ActionButtonsResolver : ValueResolver> diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 453e1567ab..384ef332e2 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.IsContainer, expression => expression.Ignore()) .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) - .AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.ProfilingLogger.Logger)); + .AfterMap((media, display) => AfterMap(media, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.Services.ContentTypeService, applicationContext.ProfilingLogger.Logger)); //FROM IMedia TO ContentItemBasic config.CreateMap>() @@ -67,34 +67,12 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); } - private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, ILogger logger) + private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService, ILogger logger) { // Adapted from ContentModelMapper //map the IsChildOfListView (this is actually if it is a descendant of a list view!) - //TODO: Fix this shorthand .Ancestors() lookup, at least have an overload to use the current - if (media.HasIdentity) - { - var ancesctorListView = media.Ancestors().FirstOrDefault(x => x.ContentType.IsContainer); - display.IsChildOfListView = ancesctorListView != null; - } - else - { - //it's new so it doesn't have a path, so we need to look this up by it's parent + ancestors - var parent = media.Parent(); - if (parent == null) - { - display.IsChildOfListView = false; - } - else if (parent.ContentType.IsContainer) - { - display.IsChildOfListView = true; - } - else - { - var ancesctorListView = parent.Ancestors().FirstOrDefault(x => x.ContentType.IsContainer); - display.IsChildOfListView = ancesctorListView != null; - } - } + var parent = media.Parent(); + display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); //map the tree node url if (HttpContext.Current != null) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 3c97551eaf..5befbda7cf 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1389,10 +1389,15 @@ namespace Umbraco.Web return test ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); } - #endregion + #endregion #region Prevalues + /// + /// 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) { return DataTypeService.GetPreValueAsString(id); diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index c86118b475..bc66e909cf 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -36,6 +36,7 @@ namespace UmbracoExamine private readonly IUserService _userService; private readonly IContentTypeService _contentTypeService; private readonly EntityXmlSerializer _serializer = new EntityXmlSerializer(); + private const int PageSize = 2; #region Constructors @@ -408,8 +409,7 @@ namespace UmbracoExamine { if (SupportedTypes.Contains(type) == false) return; - - const int pageSize = 10000; + var pageIndex = 0; DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - Start data queries - {0}", type)); @@ -445,6 +445,7 @@ namespace UmbracoExamine //sorted by: umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder var result = _contentService.GetPagedXmlEntries(path, pIndex, pSize, out totalContent).ToArray(); + var more = result.Length == pSize; //then like we do in the ContentRepository.BuildXmlCache we need to track what Parents have been processed // already so that we can then exclude implicitly unpublished content items @@ -480,7 +481,7 @@ namespace UmbracoExamine filtered.Add(xml); } - return new Tuple(totalContent, filtered.ToArray()); + return Tuple.Create(filtered.ToArray(), more); }, i => _contentService.GetById(i)); } @@ -498,13 +499,13 @@ namespace UmbracoExamine IContent[] descendants; if (SupportUnpublishedContent) { - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "umbracoNode.id").ToArray(); + descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, PageSize, out total, "umbracoNode.id").ToArray(); } else { //get all paged records but order by level ascending, we need to do this because we need to track which nodes are not published so that we can determine // which descendent nodes are implicitly not published - descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "level", Direction.Ascending, true, (string)null).ToArray(); + descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, PageSize, out total, "level", Direction.Ascending, true, (string)null).ToArray(); } // need to store decendants count before filtering, in order for loop to work correctly @@ -528,7 +529,7 @@ namespace UmbracoExamine content, notPublished).WhereNotNull(), type); pageIndex++; - } while (currentPageSize == pageSize); + } while (currentPageSize == PageSize); } break; @@ -546,7 +547,8 @@ namespace UmbracoExamine { long totalMedia; var result = _mediaService.GetPagedXmlEntries(path, pIndex, pSize, out totalMedia).ToArray(); - return new Tuple(totalMedia, result); + var more = result.Length == pSize; + return Tuple.Create(result, more); }, i => _mediaService.GetById(i)); @@ -574,39 +576,37 @@ namespace UmbracoExamine string type, int parentId, Func getContentTypes, - Func> getPagedXmlEntries, + Func> getPagedXmlEntries, Func getContent) where TContentType: IContentTypeComposition { - const int pageSize = 10000; - var pageIndex = 0; - - XElement[] xElements; + var pageIndex = 0; var contentTypes = getContentTypes(); var icons = contentTypes.ToDictionary(x => x.Id, y => y.Icon); + var parent = parentId == -1 ? null : getContent(parentId); + bool more; do { - long total; + XElement[] xElements; + if (parentId == -1) { - var pagedElements = getPagedXmlEntries("-1", pageIndex, pageSize); - total = pagedElements.Item1; - xElements = pagedElements.Item2; + var pagedElements = getPagedXmlEntries("-1", pageIndex, PageSize); + xElements = pagedElements.Item1; + more = pagedElements.Item2; + } + else if (parent == null) + { + xElements = new XElement[0]; + more = false; } else { - //Get the parent - var parent = getContent(parentId); - if (parent == null) - xElements = new XElement[0]; - else - { - var pagedElements = getPagedXmlEntries(parent.Path, pageIndex, pageSize); - total = pagedElements.Item1; - xElements = pagedElements.Item2; - } + var pagedElements = getPagedXmlEntries(parent.Path, pageIndex, PageSize); + xElements = pagedElements.Item1; + more = pagedElements.Item2; } //if specific types are declared we need to post filter them @@ -627,7 +627,7 @@ namespace UmbracoExamine AddNodesToIndex(xElements, type); pageIndex++; - } while (xElements.Length == pageSize); + } while (more); } internal static IEnumerable GetSerializedContent( diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 9ca61164eb..1a3c9befd2 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -117,7 +117,7 @@ namespace UmbracoExamine if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) { var field = new IndexField { Name = "_searchEmail" }; - + StaticField policy; if (IndexFieldPolicies.TryGetValue("_searchEmail", out policy)) { @@ -173,7 +173,8 @@ namespace UmbracoExamine { long totalContent; var result = _memberService.GetPagedXmlEntries(pIndex, pSize, out totalContent).ToArray(); - return new Tuple(totalContent, result); + var more = result.Length == pSize; + return Tuple.Create(result, more); }, i => _memberService.GetById(i)); } @@ -219,7 +220,7 @@ namespace UmbracoExamine { stopwatch.Stop(); } - + DataService.LogService.AddInfoLog(-1, string.Format("PerformIndexAll - End data queries - {0}, took {1}ms", type, stopwatch.ElapsedMilliseconds)); } diff --git a/src/umbraco.cms/businesslogic/Packager/PackageActions/addDashboardSection.cs b/src/umbraco.cms/businesslogic/Packager/PackageActions/addDashboardSection.cs index 6025123bc4..ceca7c21ff 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageActions/addDashboardSection.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageActions/addDashboardSection.cs @@ -43,23 +43,28 @@ namespace umbraco.cms.businesslogic.packager.standardPackageActions if (xmlData.HasChildNodes) { - string sectionAlias = xmlData.Attributes["dashboardAlias"].Value; - string dbConfig = SystemFiles.DashboardConfig; + string sectionAlias = xmlData.Attributes["dashboardAlias"].Value; + string dbConfig = SystemFiles.DashboardConfig; - XmlNode section = xmlData.SelectSingleNode("./section"); - XmlDocument dashboardFile = XmlHelper.OpenAsXmlDocument(dbConfig); + XmlNode section = xmlData.SelectSingleNode("./section"); + XmlDocument dashboardFile = XmlHelper.OpenAsXmlDocument(dbConfig); - XmlNode importedSection = dashboardFile.ImportNode(section, true); + //don't continue if it already exists + var found = dashboardFile.SelectNodes("//section[@alias='" + sectionAlias + "']"); + if (found == null || found.Count <= 0) + { + XmlNode importedSection = dashboardFile.ImportNode(section, true); - XmlAttribute alias = XmlHelper.AddAttribute(dashboardFile, "alias", sectionAlias); - importedSection.Attributes.Append(alias); + XmlAttribute alias = XmlHelper.AddAttribute(dashboardFile, "alias", sectionAlias); + importedSection.Attributes.Append(alias); - dashboardFile.DocumentElement.AppendChild(importedSection); + dashboardFile.DocumentElement.AppendChild(importedSection); - dashboardFile.Save(IOHelper.MapPath(dbConfig)); + dashboardFile.Save(IOHelper.MapPath(dbConfig)); + } - return true; - } + return true; + } return false; }