diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index 3bd1d581a8..5ded474d83 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -23,6 +23,33 @@ namespace Umbraco.Core.Cache } protected override DictionaryCacheWrapper DictionaryCache + /// Clears all objects in the System.Web.Cache with the System.Type specified that satisfy the predicate + /// + public override void ClearCacheObjectTypes(Func predicate) + { + try + { + lock (Locker) + { + foreach (DictionaryEntry c in _cache) + { + var key = c.Key.ToString(); + if (_cache[key] != null + && _cache[key] is T + && predicate(key, (T)_cache[key])) + { + _cache.Remove(c.Key.ToString()); + } + } + } + } + catch (Exception e) + { + LogHelper.Error("Cache clearing error", e); + } + } + + /// { get { return _wrapper; } } diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index 4feef3f8a5..ba88637661 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -27,6 +27,9 @@ namespace Umbraco.Core.Cache { } + + + public virtual void ClearCacheByKeySearch(string keyStartsWith) { } diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 8bb80670d8..0de92e1259 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -74,6 +74,14 @@ namespace Umbraco.Core { } + internal void ClearStaticCacheObjectTypes(Func predicate) + { + if (_enableCache) + _staticCache.ClearCacheObjectTypes(predicate); + else + _nullStaticCache.ClearCacheObjectTypes(predicate); + } + /// /// Private ctor used for creating a disabled cache helper /// diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index 4b50d835fe..29e4c472b7 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -65,12 +65,12 @@ namespace Umbraco.Core.Models.Rdbms return Date.Value; } - if(!string.IsNullOrEmpty(VarChar)) + if(string.IsNullOrEmpty(VarChar) == false) { return VarChar; } - if(!string.IsNullOrEmpty(Text)) + if(string.IsNullOrEmpty(Text) == false) { return Text; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index e41f904771..2695f1ee03 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -179,7 +179,7 @@ namespace Umbraco.Core.Persistence.Repositories .From() .InnerJoin().On(left => left.VersionId, right => right.VersionId) .Where(x => x.VersionId == versionId) - .Where(x => x.Newest == true); + .Where(x => x.Newest != true); var dto = Database.Fetch(sql).FirstOrDefault(); if(dto == null) return; @@ -192,6 +192,29 @@ namespace Umbraco.Core.Persistence.Repositories } } + public override void DeleteVersions(int id, DateTime versionDate) + { + var sql = new Sql() + .Select("*") + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.NodeId == id) + .Where(x => x.VersionDate < versionDate) + .Where(x => x.Newest != true); + var list = Database.Fetch(sql); + if (list.Any() == false) return; + + using (var transaction = Database.GetTransaction()) + { + foreach (var dto in list) + { + PerformDeleteVersion(id, dto.VersionId); + } + + transaction.Complete(); + } + } + protected override void PerformDeleteVersion(int id, Guid versionId) { Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 144e2a9d8f..aa754c765d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -61,7 +61,8 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); - return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)); + return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)) + .ToArray(); // important so we don't iterate twice, if we don't do thsi we can end up with null vals in cache if we were caching. } private IEnumerable PerformGetAllOnIds(params int[] ids) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 64e0e833f0..1b3ff63eea 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -46,6 +46,11 @@ namespace Umbraco.Core.Persistence.Repositories var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); if(dto == null) return; + //Ensure that the lastest version is not deleted + var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = dto.NodeId }); + if(latestVersionDto.VersionId == dto.VersionId) + return; + using (var transaction = Database.GetTransaction()) { PerformDeleteVersion(dto.NodeId, versionId); @@ -56,7 +61,12 @@ namespace Umbraco.Core.Persistence.Repositories public virtual void DeleteVersions(int id, DateTime versionDate) { - var list = Database.Fetch("WHERE ContentId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate }); + //Ensure that the latest version is not part of the versions being deleted + var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = id }); + var list = + Database.Fetch( + "WHERE versionId <> @VersionId AND (ContentId = @Id AND VersionDate < @VersionDate)", + new {VersionId = latestVersionDto.VersionId, Id = id, VersionDate = versionDate}); if (list.Any() == false) return; using (var transaction = Database.GetTransaction()) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index fc8daac769..d4437a5000 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -830,15 +830,13 @@ namespace Umbraco.Core.Services /// /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a content item. /// /// Id of the object to delete versions from /// Latest version date /// Optional Id of the User deleting versions of a Content object public void DeleteVersions(int id, DateTime versionDate, int userId = 0) { - //TODO: We should check if we are going to delete the most recent version because if that happens it means the - // entity is completely deleted and we should raise the normal Deleting/Deleted event - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) return; @@ -856,6 +854,7 @@ namespace Umbraco.Core.Services /// /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a content item. /// /// Id of the object to delete a version from /// Id of the version to delete @@ -865,8 +864,8 @@ namespace Umbraco.Core.Services { using (new WriteLock(Locker)) { - //TODO: We should check if we are going to delete the most recent version because if that happens it means the - // entity is completely deleted and we should raise the normal Deleting/Deleted event + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) + return; if (deletePriorVersions) { @@ -874,9 +873,6 @@ namespace Umbraco.Core.Services DeleteVersions(id, content.UpdateDate, userId); } - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) - return; - var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) { diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index f3a967c096..f6a42aea9b 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -654,6 +654,7 @@ namespace Umbraco.Core.Services /// /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a content item. /// /// Id of the object to delete versions from /// Latest version date @@ -677,6 +678,7 @@ namespace Umbraco.Core.Services /// /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a content item. /// /// Id of the object to delete a version from /// Id of the version to delete @@ -684,15 +686,15 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting versions of a Content object public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) { + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) + return; + if (deletePriorVersions) { var content = GetByVersion(versionId); DeleteVersions(id, content.UpdateDate, userId); } - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion:versionId), this)) - return; - var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMediaRepository(uow)) { diff --git a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs index f292dc9082..7aaba12df1 100644 --- a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs +++ b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs @@ -150,6 +150,8 @@ namespace Umbraco.Tests.Publishing var result1 = strategy.Publish(_homePage, 0); Assert.IsTrue(result1); Assert.IsTrue(_homePage.Published); + + //NOTE (MCH) This isn't persisted, so not really a good test as it will look like the result should be something else. foreach (var c in ServiceContext.ContentService.GetChildren(_homePage.Id)) { var r = strategy.Publish(c, 0); @@ -157,15 +159,20 @@ namespace Umbraco.Tests.Publishing Assert.IsTrue(c.Published); } - //ok, all are published except the deepest descendant, we will pass in a flag to include it to - //be published - var result = strategy.PublishWithChildrenInternal( - ServiceContext.ContentService.GetDescendants(_homePage).Concat(new[] { _homePage }), 0, true); - //there will be 4 here but only one "Success" the rest will be "SuccessAlreadyPublished" - Assert.AreEqual(1, result.Count(x => x.Result.StatusType == PublishStatusType.Success)); - Assert.AreEqual(3, result.Count(x => x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished)); - Assert.IsTrue(result.Single(x => x.Result.StatusType == PublishStatusType.Success).Success); - Assert.IsTrue(result.Single(x => x.Result.StatusType == PublishStatusType.Success).Result.ContentItem.Published); + //NOTE (MCH) when doing the test like this the Publish status will not actually have been persisted + //since its only updating a property. The actual persistence and publishing is done through the ContentService. + //So when descendants are fetched from the ContentService the Publish status will be "reset", which + //means the result will be 1 'SuccessAlreadyPublished' and 3 'Success' because the Homepage is + //inserted in the list and since that item has the status of already being Published it will be the one item + //with 'SuccessAlreadyPublished' + + var descendants = ServiceContext.ContentService.GetDescendants(_homePage).Concat(new[] {_homePage}); + var result = strategy.PublishWithChildrenInternal(descendants, 0, true); + + Assert.AreEqual(3, result.Count(x => x.Result.StatusType == PublishStatusType.Success)); + Assert.AreEqual(1, result.Count(x => x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished)); + Assert.IsTrue(result.First(x => x.Result.StatusType == PublishStatusType.Success).Success); + Assert.IsTrue(result.First(x => x.Result.StatusType == PublishStatusType.Success).Result.ContentItem.Published); } [NUnit.Framework.Ignore] diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 089fc25ae2..e15f05817d 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -842,12 +842,14 @@ namespace Umbraco.Tests.Services Assert.That(sut.GetValue("multilineText"), Is.EqualTo("Multiple lines \n in one box")); Assert.That(sut.GetValue("upload"), Is.EqualTo("/media/1234/koala.jpg")); Assert.That(sut.GetValue("label"), Is.EqualTo("Non-editable label")); - Assert.That(sut.GetValue("dateTime"), Is.EqualTo(content.GetValue("dateTime"))); + //SD: This is failing because the 'content' call to GetValue always has empty milliseconds + //MCH: I'm guessing this is an issue because of the format the date is actually stored as, right? Cause we don't do any formatting when saving or loading + Assert.That(sut.GetValue("dateTime").ToString("G"), Is.EqualTo(content.GetValue("dateTime").ToString("G"))); Assert.That(sut.GetValue("colorPicker"), Is.EqualTo("black")); Assert.That(sut.GetValue("folderBrowser"), Is.Empty); Assert.That(sut.GetValue("ddlMultiple"), Is.EqualTo("1234,1235")); Assert.That(sut.GetValue("rbList"), Is.EqualTo("random")); - Assert.That(sut.GetValue("date"), Is.EqualTo(content.GetValue("date"))); + Assert.That(sut.GetValue("date").ToString("G"), Is.EqualTo(content.GetValue("date").ToString("G"))); Assert.That(sut.GetValue("ddl"), Is.EqualTo("1234")); Assert.That(sut.GetValue("chklist"), Is.EqualTo("randomc")); Assert.That(sut.GetValue("contentPicker"), Is.EqualTo(1090)); diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index de120db8ac..fc1d1c77d0 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -50,10 +50,13 @@ namespace Umbraco.Web.Search if (registeredProviders == 0) return; + MediaService.Created += MediaServiceCreated; MediaService.Saved += MediaServiceSaved; MediaService.Deleted += MediaServiceDeleted; MediaService.Moved += MediaServiceMoved; MediaService.Trashed += MediaServiceTrashed; + + ContentService.Created += ContentServiceCreated; ContentService.Saved += ContentServiceSaved; ContentService.Deleted += ContentServiceDeleted; ContentService.Moved += ContentServiceMoved; @@ -78,6 +81,18 @@ namespace Umbraco.Web.Search } } + [SecuritySafeCritical] + static void ContentServiceCreated(IContentService sender, Core.Events.NewEventArgs e) + { + IndexConent(e.Entity); + } + + [SecuritySafeCritical] + static void MediaServiceCreated(IMediaService sender, Core.Events.NewEventArgs e) + { + IndexMedia(e.Entity); + } + [SecuritySafeCritical] static void ContentServiceTrashed(IContentService sender, Core.Events.MoveEventArgs e) { diff --git a/src/umbraco.editorControls/datepicker/DateData.cs b/src/umbraco.editorControls/datepicker/DateData.cs index 56d213d992..688218f317 100644 --- a/src/umbraco.editorControls/datepicker/DateData.cs +++ b/src/umbraco.editorControls/datepicker/DateData.cs @@ -12,10 +12,17 @@ namespace umbraco.editorControls.datepicker public override System.Xml.XmlNode ToXMl(System.Xml.XmlDocument d) { - if (Value != null && Value.ToString() != "") - return d.CreateTextNode(((DateTime) Value).ToString("s")); - else - return d.CreateTextNode(""); + if (Value != null && Value.ToString() != "") + { + if(Value is DateTime) + return d.CreateTextNode(((DateTime) Value).ToString("s")); + + DateTime convertedDate; + if (DateTime.TryParse(Value.ToString(), out convertedDate)) + return d.CreateTextNode(convertedDate.ToString("s")); + } + + return d.CreateTextNode(""); } public override void MakeNew(int PropertyId)