diff --git a/src/Umbraco.Core/Events/EventNameExtractor.cs b/src/Umbraco.Core/Events/EventNameExtractor.cs index 5cb9ca64ef..627426f2ee 100644 --- a/src/Umbraco.Core/Events/EventNameExtractor.cs +++ b/src/Umbraco.Core/Events/EventNameExtractor.cs @@ -35,6 +35,24 @@ namespace Umbraco.Core.Events /// null if not found or an ambiguous match /// public static Attempt FindEvent(Type senderType, Type argsType, Func exclude) + { + var events = FindEvents(senderType, argsType, exclude); + + switch (events.Length) + { + case 0: + return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.NoneFound)); + + case 1: + return Attempt.Succeed(new EventNameExtractorResult(events[0])); + + default: + //there's more than one left so it's ambiguous! + return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); + } + } + + internal static string[] FindEvents(Type senderType, Type argsType, Func exclude) { var found = MatchedEventNames.GetOrAdd(new Tuple(senderType, argsType), tuple => { @@ -78,16 +96,7 @@ namespace Umbraco.Core.Events }).Select(x => x.EventInfo.Name).ToArray(); }); - var filtered = found.Where(x => exclude(x) == false).ToArray(); - - if (filtered.Length == 0) - return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.NoneFound)); - - if (filtered.Length == 1) - return Attempt.Succeed(new EventNameExtractorResult(filtered[0])); - - //there's more than one left so it's ambiguous! - return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); + return found.Where(x => exclude(x) == false).ToArray(); } /// diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 0c679e5e70..1093ef3a0c 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -15,14 +15,12 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Content : ContentBase, IContent { - private IContentType _contentType; private int? _templateId; private ContentScheduleCollection _schedule; private bool _published; private PublishedState _publishedState; - private ContentCultureInfosCollection _publishInfos; - private ContentCultureInfosCollection _publishInfosOrig; private HashSet _editedCultures; + private ContentCultureInfosCollection _publishInfos, _publishInfos1, _publishInfos2; private static readonly Lazy Ps = new Lazy(); @@ -48,7 +46,7 @@ namespace Umbraco.Core.Models public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, string culture = null) : base(name, parent, contentType, properties, culture) { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); _publishedState = PublishedState.Unpublished; PublishedVersionId = 0; } @@ -75,7 +73,7 @@ namespace Umbraco.Core.Models public Content(string name, int parentId, IContentType contentType, PropertyCollection properties, string culture = null) : base(name, parentId, contentType, properties, culture) { - _contentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); _publishedState = PublishedState.Unpublished; PublishedVersionId = 0; } @@ -137,7 +135,6 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _templateId, Ps.Value.TemplateSelector); } - /// /// Gets or sets a value indicating whether this content item is published or not. /// @@ -181,7 +178,7 @@ namespace Umbraco.Core.Models /// Gets the ContentType used by this content object /// [IgnoreDataMember] - public IContentType ContentType => _contentType; + public IContentType ContentType { get; private set; } /// [IgnoreDataMember] @@ -217,7 +214,7 @@ namespace Umbraco.Core.Models public bool WasCulturePublished(string culture) // just check _publishInfosOrig - a copy of _publishInfos // a non-available culture could not become published anyways - => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); + => _publishInfos1 != null && _publishInfos1.ContainsKey(culture); // adjust dates to sync between version, cultures etc // used by the repo when persisting @@ -228,7 +225,7 @@ namespace Umbraco.Core.Models if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos)) continue; - if (_publishInfosOrig != null && _publishInfosOrig.TryGetValue(culture, out var publishInfosOrig) + if (_publishInfos1 != null && _publishInfos1.TryGetValue(culture, out var publishInfosOrig) && publishInfosOrig.Date == publishInfos.Date) continue; @@ -285,6 +282,24 @@ namespace Umbraco.Core.Models _publishInfos.AddOrUpdate(culture, name, date); } + // internal for repository + internal void AknPublishInfo() + { + _publishInfos1 = _publishInfos2 = new ContentCultureInfosCollection(_publishInfos); + } + + /// + public bool IsPublishingCulture(string culture) => _publishInfos.IsCultureUpdated(_publishInfos1, culture); + + /// + public bool IsUnpublishingCulture(string culture) => _publishInfos.IsCultureRemoved(_publishInfos1, culture); + + /// + public bool HasPublishedCulture(string culture) => _publishInfos1.IsCultureUpdated(_publishInfos2, culture); + + /// + public bool HasUnpublishedCulture(string culture) => _publishInfos1.IsCultureRemoved(_publishInfos2, culture); + private void ClearPublishInfos() { _publishInfos = null; @@ -300,7 +315,7 @@ namespace Umbraco.Core.Models if (_publishInfos.Count == 0) _publishInfos = null; // set the culture to be dirty - it's been modified - TouchCultureInfo(culture); + TouchCulture(culture); } // sets a publish edited @@ -423,7 +438,7 @@ namespace Umbraco.Core.Models public void ChangeContentType(IContentType contentType) { ContentTypeId = contentType.Id; - _contentType = contentType; + ContentType = contentType; ContentTypeBase = contentType; Properties.EnsurePropertyTypes(PropertyTypes); @@ -442,7 +457,7 @@ namespace Umbraco.Core.Models if(clearProperties) { ContentTypeId = contentType.Id; - _contentType = contentType; + ContentType = contentType; ContentTypeBase = contentType; Properties.EnsureCleanPropertyTypes(PropertyTypes); @@ -457,16 +472,18 @@ namespace Umbraco.Core.Models public override void ResetDirtyProperties(bool rememberDirty) { base.ResetDirtyProperties(rememberDirty); - + if (ContentType != null) ContentType.ResetDirtyProperties(rememberDirty); // take care of the published state _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; + _publishInfos2 = _publishInfos1; + // Make a copy of the _publishInfos, this is purely so that we can detect // if this entity's previous culture publish state (regardless of the rememberDirty flag) - _publishInfosOrig = _publishInfos == null + _publishInfos1 = _publishInfos == null ? null : new ContentCultureInfosCollection(_publishInfos); @@ -500,7 +517,7 @@ namespace Umbraco.Core.Models var clonedContent = (Content)clone; //need to manually clone this since it's not settable - clonedContent._contentType = (IContentType) ContentType.DeepClone(); + clonedContent.ContentType = (IContentType) ContentType.DeepClone(); //if culture infos exist then deal with event bindings if (clonedContent._publishInfos != null) diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index ca1152a9a4..e540866c3e 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Models protected IContentTypeComposition ContentTypeBase; private int _writerId; private PropertyCollection _properties; - private ContentCultureInfosCollection _cultureInfos; + private ContentCultureInfosCollection _cultureInfos, _cultureInfos1, _cultureInfos2; /// /// Initializes a new instance of the class. @@ -222,7 +222,8 @@ namespace Umbraco.Core.Models _cultureInfos = null; } - protected void TouchCultureInfo(string culture) + /// + public void TouchCulture(string culture) { if (_cultureInfos == null || !_cultureInfos.TryGetValue(culture, out var infos)) return; _cultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now); @@ -400,6 +401,9 @@ namespace Umbraco.Core.Models foreach (var prop in Properties) prop.ResetDirtyProperties(rememberDirty); + _cultureInfos2 = _cultureInfos1; + _cultureInfos1 = _cultureInfos == null ? null : new ContentCultureInfosCollection(_cultureInfos); + // take care of culture infos if (_cultureInfos == null) return; @@ -475,6 +479,12 @@ namespace Umbraco.Core.Models return instanceProperties.Concat(propertyTypes); } + /// + public bool IsSavingCulture(string culture) => _cultureInfos.IsCultureUpdated(_cultureInfos1, culture); + + /// + public bool HasSavedCulture(string culture) => _cultureInfos1.IsCultureUpdated(_cultureInfos2, culture); + #endregion /// diff --git a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs index 82b0ba6475..287182d20e 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Models foreach (var item in items) Add(new ContentCultureInfos(item)); } - + /// /// Adds or updates a instance. /// @@ -53,7 +53,7 @@ namespace Umbraco.Core.Models Name = name, Date = date }); - } + } } /// diff --git a/src/Umbraco.Core/Models/ContentCultureInfosCollectionExtensions.cs b/src/Umbraco.Core/Models/ContentCultureInfosCollectionExtensions.cs new file mode 100644 index 0000000000..98a0b48d07 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentCultureInfosCollectionExtensions.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Models +{ + public static class ContentCultureInfosCollectionExtensions + { + public static bool IsCultureUpdated(this ContentCultureInfosCollection to, ContentCultureInfosCollection from, string culture) + => to != null && to.ContainsKey(culture) && + (from == null || !from.ContainsKey(culture) || from[culture].Date != to[culture].Date); + + public static bool IsCultureRemoved(this ContentCultureInfosCollection to, ContentCultureInfosCollection from, string culture) + => (to == null || !to.ContainsKey(culture)) && from != null && from.ContainsKey(culture); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 4363324c8d..d0f47bc2f2 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -177,5 +177,29 @@ namespace Umbraco.Core.Models /// Unpublishing must be finalized via the content service SavePublishing method. /// void UnpublishCulture(string culture = "*"); + + /// + /// Determines whether a culture is being published, during a Publishing event. + /// + /// Outside of a Publishing event handler, the returned value is unspecified. + bool IsPublishingCulture(string culture); + + /// + /// Determines whether a culture is being unpublished, during a Publishing event. + /// + /// Outside of a Publishing event handler, the returned value is unspecified. + bool IsUnpublishingCulture(string culture); + + /// + /// Determines whether a culture has been published, during a Published event. + /// + /// Outside of a Published event handler, the returned value is unspecified. + bool HasPublishedCulture(string culture); + + /// + /// Determines whether a culture has been unpublished, during a Published event. + /// + /// Outside of a Published event handler, the returned value is unspecified. + bool HasUnpublishedCulture(string culture); } } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 40a1c57097..d2a928f978 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -143,5 +143,22 @@ namespace Umbraco.Core.Models /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. Property[] ValidateProperties(string culture = "*"); + + /// + /// Determines whether a culture is being saved, during a Saving event. + /// + /// Outside of a Saving event handler, the returned value is unspecified. + bool IsSavingCulture(string culture); + + /// + /// Determines whether a culture has been saved, during a Saved event. + /// + /// Outside of a Saved event handler, the returned value is unspecified. + bool HasSavedCulture(string culture); + + /// + /// Updates a culture date, if the culture exists. + /// + void TouchCulture(string culture); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 6af7031883..70f3bd8071 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1188,8 +1188,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var v in contentVariation) content.SetCultureInfo(v.Culture, v.Name, v.Date); if (content.PublishedVersionId > 0 && contentVariations.TryGetValue(content.PublishedVersionId, out contentVariation)) + { foreach (var v in contentVariation) content.SetPublishInfo(v.Culture, v.Name, v.Date); + content.AknPublishInfo(); + } if (documentVariations.TryGetValue(content.Id, out var documentVariation)) foreach (var v in documentVariation.Where(x => x.Edited)) content.SetCultureEdited(v.Culture); diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 266f34cc37..804fe35dd1 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -27,18 +26,16 @@ namespace Umbraco.Core.Services.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly IDocumentBlueprintRepository _documentBlueprintRepository; private readonly ILanguageRepository _languageRepository; - private readonly IMediaFileSystem _mediaFileSystem; private IQuery _queryNotTrashed; #region Constructors public ContentService(IScopeProvider provider, ILogger logger, - IEventMessagesFactory eventMessagesFactory, IMediaFileSystem mediaFileSystem, + IEventMessagesFactory eventMessagesFactory, IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository, IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository) : base(provider, logger, eventMessagesFactory) { - _mediaFileSystem = mediaFileSystem; _documentRepository = documentRepository; _entityRepository = entityRepository; _auditRepository = auditRepository; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4e6e832294..e4fbfb3892 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -388,6 +388,7 @@ + diff --git a/src/Umbraco.Tests/Services/AmbiguousEventTests.cs b/src/Umbraco.Tests/Services/AmbiguousEventTests.cs new file mode 100644 index 0000000000..e137da1188 --- /dev/null +++ b/src/Umbraco.Tests/Services/AmbiguousEventTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Reflection; +using System.Text; +using NUnit.Framework; +using Umbraco.Core.Events; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class AmbiguousEventTests + { + [Explicit] + [TestCase(typeof(ContentService))] + [TestCase(typeof(MediaService))] + public void ListAmbiguousEvents(Type serviceType) + { + var typedEventHandler = typeof(TypedEventHandler<,>); + + // get all events + var events = serviceType.GetEvents(BindingFlags.Static | BindingFlags.Public); + + string TypeName(Type type) + { + if (!type.IsGenericType) + return type.Name; + var sb = new StringBuilder(); + TypeNameSb(type, sb); + return sb.ToString(); + } + + void TypeNameSb(Type type, StringBuilder sb) + { + var name = type.Name; + var pos = name.IndexOf('`'); + name = pos > 0 ? name.Substring(0, pos) : name; + sb.Append(name); + if (!type.IsGenericType) + return; + sb.Append("<"); + var first = true; + foreach (var arg in type.GetGenericArguments()) + { + if (first) first = false; + else sb.Append(", "); + TypeNameSb(arg, sb); + } + sb.Append(">"); + } + + foreach (var e in events) + { + // only continue if this is a TypedEventHandler + if (!e.EventHandlerType.IsGenericType) continue; + var typeDef = e.EventHandlerType.GetGenericTypeDefinition(); + if (typedEventHandler != typeDef) continue; + + // get the event args type + var eventArgsType = e.EventHandlerType.GenericTypeArguments[1]; + + // try to find the event back, based upon sender type + args type + // exclude -ing (eg Saving) events, we don't deal with them in EventDefinitionBase (they always trigger) + var found = EventNameExtractor.FindEvents(serviceType, eventArgsType, EventNameExtractor.MatchIngNames); + + if (found.Length == 1) continue; + + if (found.Length == 0) + { + Console.WriteLine($"{typeof(ContentService).Name} {e.Name} {TypeName(eventArgsType)} NotFound"); + continue; + } + + Console.WriteLine($"{typeof(ContentService).Name} {e.Name} {TypeName(eventArgsType)} Ambiguous"); + Console.WriteLine("\t" + string.Join(", ", found)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/ContentServiceEventTests.cs b/src/Umbraco.Tests/Services/ContentServiceEventTests.cs new file mode 100644 index 0000000000..3576425b9c --- /dev/null +++ b/src/Umbraco.Tests/Services/ContentServiceEventTests.cs @@ -0,0 +1,224 @@ +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, + PublishedRepositoryEvents = true, + WithApplication = true, + Logger = UmbracoTestOptions.Logger.Console)] + public class ContentServiceEventTests : TestWithSomeContentBase + { + public override void SetUp() + { + base.SetUp(); + ContentRepositoryBase.ThrowOnWarning = true; + } + + public override void TearDown() + { + ContentRepositoryBase.ThrowOnWarning = false; + base.TearDown(); + } + + [Test] + public void SavingTest() + { + var languageService = ServiceContext.LocalizationService; + + languageService.Save(new Language("fr-FR")); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentType.Variations = ContentVariation.Culture; + foreach (var propertyType in contentType.PropertyTypes) + propertyType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + var document = new Content("content", -1, contentType); + document.SetCultureName("hello", "en-US"); + document.SetCultureName("bonjour", "fr-FR"); + contentService.Save(document); + + // properties: title, bodyText, keywords, description + document.SetValue("title", "title-en", "en-US"); + + // touch the culture - required for IsSaving/HasSaved to work + document.TouchCulture("fr-FR"); + + void OnSaving(IContentService sender, SaveEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.AreSame(document, saved); + + Assert.IsTrue(saved.IsSavingCulture("fr-FR")); + Assert.IsFalse(saved.IsSavingCulture("en-UK")); + } + + void OnSaved(IContentService sender, SaveEventArgs e) + { + var saved = e.SavedEntities.First(); + + Assert.AreSame(document, saved); + + Assert.IsTrue(saved.HasSavedCulture("fr-FR")); + Assert.IsFalse(saved.HasSavedCulture("en-UK")); + } + + ContentService.Saving += OnSaving; + ContentService.Saved += OnSaved; + contentService.Save(document); + ContentService.Saving -= OnSaving; + ContentService.Saved -= OnSaved; + } + + [Test] + public void PublishingTest() + { + var languageService = ServiceContext.LocalizationService; + + languageService.Save(new Language("fr-FR")); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentType.Variations = ContentVariation.Culture; + foreach (var propertyType in contentType.PropertyTypes) + propertyType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + var document = new Content("content", -1, contentType); + document.SetCultureName("hello", "en-US"); + document.SetCultureName("bonjour", "fr-FR"); + contentService.Save(document); + + // ensure it works and does not throw + Assert.IsFalse(document.WasCulturePublished("fr-FR")); + Assert.IsFalse(document.WasCulturePublished("en-US")); + Assert.IsFalse(document.IsCulturePublished("fr-FR")); + Assert.IsFalse(document.IsCulturePublished("en-US")); + + void OnPublishing(IContentService sender, PublishEventArgs e) + { + var publishing = e.PublishedEntities.First(); + + Assert.AreSame(document, publishing); + + Assert.IsFalse(publishing.IsPublishingCulture("en-US")); + Assert.IsTrue(publishing.IsPublishingCulture("fr-FR")); + } + + void OnPublished(IContentService sender, PublishEventArgs e) + { + var published = e.PublishedEntities.First(); + + Assert.AreSame(document, published); + + Assert.IsFalse(published.HasPublishedCulture("en-US")); + Assert.IsTrue(published.HasPublishedCulture("fr-FR")); + } + + ContentService.Publishing += OnPublishing; + ContentService.Published += OnPublished; + contentService.SaveAndPublish(document, "fr-FR"); + ContentService.Publishing -= OnPublishing; + ContentService.Published -= OnPublished; + + document = (Content) contentService.GetById(document.Id); + + // ensure it works and does not throw + Assert.IsTrue(document.WasCulturePublished("fr-FR")); + Assert.IsFalse(document.WasCulturePublished("en-US")); + Assert.IsTrue(document.IsCulturePublished("fr-FR")); + Assert.IsFalse(document.IsCulturePublished("en-US")); + } + + [Test] + public void UnpublishingTest() + { + var languageService = ServiceContext.LocalizationService; + + languageService.Save(new Language("fr-FR")); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = MockedContentTypes.CreateTextPageContentType(); + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); + contentType.Variations = ContentVariation.Culture; + foreach (var propertyType in contentType.PropertyTypes) + propertyType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + var document = new Content("content", -1, contentType); + document.SetCultureName("hello", "en-US"); + document.SetCultureName("bonjour", "fr-FR"); + contentService.SaveAndPublish(document); + + // ensure it works and does not throw + Assert.IsTrue(document.WasCulturePublished("fr-FR")); + Assert.IsTrue(document.WasCulturePublished("en-US")); + Assert.IsTrue(document.IsCulturePublished("fr-FR")); + Assert.IsTrue(document.IsCulturePublished("en-US")); + + void OnPublishing(IContentService sender, PublishEventArgs e) + { + var publishing = e.PublishedEntities.First(); + + Assert.AreSame(document, publishing); + + Assert.IsFalse(publishing.IsPublishingCulture("en-US")); + Assert.IsFalse(publishing.IsPublishingCulture("fr-FR")); + + Assert.IsFalse(publishing.IsUnpublishingCulture("en-US")); + Assert.IsTrue(publishing.IsUnpublishingCulture("fr-FR")); + } + + void OnPublished(IContentService sender, PublishEventArgs e) + { + var published = e.PublishedEntities.First(); + + Assert.AreSame(document, published); + + Assert.IsFalse(published.HasPublishedCulture("en-US")); + Assert.IsFalse(published.HasPublishedCulture("fr-FR")); + + Assert.IsFalse(published.HasUnpublishedCulture("en-US")); + Assert.IsTrue(published.HasUnpublishedCulture("fr-FR")); + } + + document.UnpublishCulture("fr-FR"); + + ContentService.Publishing += OnPublishing; + ContentService.Published += OnPublished; + contentService.SavePublishing(document); + ContentService.Publishing -= OnPublishing; + ContentService.Published -= OnPublished; + + document = (Content) contentService.GetById(document.Id); + + // ensure it works and does not throw + Assert.IsFalse(document.WasCulturePublished("fr-FR")); + Assert.IsTrue(document.WasCulturePublished("en-US")); + Assert.IsFalse(document.IsCulturePublished("fr-FR")); + Assert.IsTrue(document.IsCulturePublished("en-US")); + } + } +} diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 37d34557bb..e339d8d1b6 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -6,7 +6,6 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -14,14 +13,11 @@ using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Events; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services.Implement; using Umbraco.Tests.Testing; -using System.Reflection; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; namespace Umbraco.Tests.Services { @@ -60,32 +56,6 @@ namespace Umbraco.Tests.Services Composition.RegisterUnique(factory => Mock.Of()); } - /// - /// Used to list out all ambiguous events that will require dispatching with a name - /// - [Test, Explicit] - public void List_Ambiguous_Events() - { - var events = ServiceContext.ContentService.GetType().GetEvents(BindingFlags.Static | BindingFlags.Public); - var typedEventHandler = typeof(TypedEventHandler<,>); - foreach(var e in events) - { - //only continue if this is a TypedEventHandler - if (!e.EventHandlerType.IsGenericType) continue; - var typeDef = e.EventHandlerType.GetGenericTypeDefinition(); - if (typedEventHandler != typeDef) continue; - - //get the event arg type - var eventArgType = e.EventHandlerType.GenericTypeArguments[1]; - - var found = EventNameExtractor.FindEvent(typeof(ContentService), eventArgType, EventNameExtractor.MatchIngNames); - if (!found.Success && found.Result.Error == EventNameExtractorError.Ambiguous) - { - Console.WriteLine($"Ambiguous event, source: {typeof(ContentService)}, args: {eventArgType}"); - } - } - } - [Test] public void Create_Blueprint() { @@ -818,7 +788,7 @@ namespace Umbraco.Tests.Services content.PublishCulture(langGB.IsoCode); content.PublishCulture(langFr.IsoCode); Assert.IsTrue(ServiceContext.ContentService.SavePublishing(content).Success); - + //re-get content = ServiceContext.ContentService.GetById(content.Id); Assert.AreEqual(PublishedState.Published, content.PublishedState); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 7a9702031b..46af5d9e71 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -153,7 +153,7 @@ namespace Umbraco.Tests.TestHelpers var localizationService = GetLazyService(factory, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); var userService = GetLazyService(factory, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); var notificationService = GetLazyService(factory, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); var serverRegistrationService = GetLazyService(factory, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var memberGroupService = GetLazyService(factory, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); @@ -176,7 +176,7 @@ namespace Umbraco.Tests.TestHelpers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var compiledPackageXmlParser = new CompiledPackageXmlParser(new ConflictingPackageData(macroService.Value, fileService.Value)); return new PackagingService( - auditService.Value, + auditService.Value, new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders), logger, "createdPackages.config"), new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value, @@ -188,7 +188,7 @@ namespace Umbraco.Tests.TestHelpers new DirectoryInfo(IOHelper.GetRootDirectorySafe()))); }); var relationService = GetLazyService(factory, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c))); - var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 986dad12fd..1703743c6f 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -141,6 +141,8 @@ + +