diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs
index 4a1f0a09b5..64099606d6 100644
--- a/src/Umbraco.Core/ContentExtensions.cs
+++ b/src/Umbraco.Core/ContentExtensions.cs
@@ -57,7 +57,7 @@ namespace Umbraco.Core
///
internal static IReadOnlyList GetCulturesUnpublishing(this IContent content)
{
- if (!content.ContentType.VariesByCulture() && !content.IsPropertyDirty("PublishCultureInfos"))
+ if (!content.ContentType.VariesByCulture() && !content.IsPropertyDirty("PublishCultureInfos") && !content.Published)
return Array.Empty();
var culturesChanging = content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key);
diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Core/Models/ContentScheduleCollection.cs
index 150390bb00..5f0a09285f 100644
--- a/src/Umbraco.Core/Models/ContentScheduleCollection.cs
+++ b/src/Umbraco.Core/Models/ContentScheduleCollection.cs
@@ -147,11 +147,9 @@ namespace Umbraco.Core.Models
///
///
///
- public IEnumerable GetPending(ContentScheduleChange changeType, DateTime date)
+ public IReadOnlyList GetPending(ContentScheduleChange changeType, DateTime date)
{
- if (_schedule.TryGetValue(string.Empty, out var changes))
- return changes.Values.Where(x => x.Date <= date);
- return Enumerable.Empty();
+ return _schedule.Values.SelectMany(x => x.Values).Where(x => x.Date <= date).ToList();
}
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index 5b377fb8bc..9e31936a62 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -1083,9 +1083,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//load in the schedule
if (schedule.TryGetValue(temp.Content.Id, out var s))
temp.Content.ContentSchedule = s;
-
- // reset dirty initial properties (U4-1946)
- temp.Content.ResetDirtyProperties(false);
}
}
@@ -1100,6 +1097,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetVariations(temp.Content, contentVariations, documentVariations);
}
+ foreach(var c in content)
+ c.ResetDirtyProperties(false); // reset dirty initial properties (U4-1946)
+
return content;
}
diff --git a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs
index c424496a38..2eb3e39ed1 100644
--- a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs
+++ b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs
@@ -31,7 +31,7 @@ namespace Umbraco.Core.Publishing
///
public int CheckPendingAndProcess()
{
- var results = _contentService.PerformScheduledPublish();
+ var results = _contentService.PerformScheduledPublish(DateTime.Now);
return results.Count(x => x.Success);
}
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index f573a829f3..b22490dd1d 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -423,7 +423,7 @@ namespace Umbraco.Core.Services
///
/// Publishes and unpublishes scheduled documents.
///
- IEnumerable PerformScheduledPublish();
+ IEnumerable PerformScheduledPublish(DateTime date);
#endregion
diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs
index a3f932d147..557cef8d06 100644
--- a/src/Umbraco.Core/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentService.cs
@@ -1174,7 +1174,7 @@ namespace Umbraco.Core.Services.Implement
}
///
- public IEnumerable PerformScheduledPublish()
+ public IEnumerable PerformScheduledPublish(DateTime date)
{
var evtMsgs = EventMessagesFactory.Get();
@@ -1182,7 +1182,7 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
- var now = DateTime.Now;
+ var now = date;
foreach (var d in GetContentForRelease(now))
{
@@ -1192,7 +1192,8 @@ namespace Umbraco.Core.Services.Implement
//find which cultures have pending schedules
var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleChange.Start, now)
.Select(x => x.Culture)
- .Distinct();
+ .Distinct()
+ .ToList();
foreach (var c in pendingCultures)
{
@@ -1202,10 +1203,13 @@ namespace Umbraco.Core.Services.Implement
d.PublishCulture(c);
}
- result = SavePublishing(d, d.WriterId);
- if (result.Success == false)
- Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
-
+ if (pendingCultures.Count > 0)
+ {
+ result = SavePublishing(d, d.WriterId);
+ if (result.Success == false)
+ Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
+ yield return result;
+ }
}
else
{
@@ -1214,9 +1218,8 @@ namespace Umbraco.Core.Services.Implement
result = SaveAndPublish(d, userId: d.WriterId);
if (result.Success == false)
Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
+ yield return result;
}
-
- yield return result;
}
foreach (var d in GetContentForExpiration(now))
@@ -1227,7 +1230,8 @@ namespace Umbraco.Core.Services.Implement
//find which cultures have pending schedules
var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleChange.End, now)
.Select(x => x.Culture)
- .Distinct();
+ .Distinct()
+ .ToList();
foreach (var c in pendingCultures)
{
@@ -1237,10 +1241,13 @@ namespace Umbraco.Core.Services.Implement
d.UnpublishCulture(c);
}
- result = SavePublishing(d, d.WriterId);
- if (result.Success == false)
- Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
-
+ if (pendingCultures.Count > 0)
+ {
+ result = SavePublishing(d, d.WriterId);
+ if (result.Success == false)
+ Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
+ yield return result;
+ }
}
else
{
@@ -1249,9 +1256,10 @@ namespace Umbraco.Core.Services.Implement
result = Unpublish(d, userId: d.WriterId);
if (result.Success == false)
Logger.Error(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
+ yield return result;
}
- yield return result;
+
}
scope.Complete();
@@ -2292,14 +2300,14 @@ namespace Umbraco.Core.Services.Implement
if (variesByCulture)
{
var publishedCultures = content.PublishedCultures.ToList();
- var cannotBePublished = publishedCultures.Count == 0; // no published cultures = cannot be published
- if (!cannotBePublished)
- {
- var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
- cannotBePublished = mandatoryCultures.Any(x => !publishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); // missing mandatory culture = cannot be published
- }
- if (cannotBePublished)
+ if (publishedCultures.Count == 0) // no published cultures = cannot be published
+ return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
+
+ var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
+ var mandatoryMissing = mandatoryCultures.Any(x => !publishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); // missing mandatory culture = cannot be published
+
+ if (mandatoryMissing)
return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content);
//track which cultures are being published
@@ -2312,7 +2320,7 @@ namespace Umbraco.Core.Services.Implement
if (((Content) content).PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0)
{
Logger.Info("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document does not have published values");
- return new PublishResult(PublishResultType.FailedPublishNoPublishedValues, evtMsgs, content);
+ return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
//loop over each culture publishing - or string.Empty for invariant
diff --git a/src/Umbraco.Core/Services/PublishResultType.cs b/src/Umbraco.Core/Services/PublishResultType.cs
index 4d2c45b10f..0dc679ba79 100644
--- a/src/Umbraco.Core/Services/PublishResultType.cs
+++ b/src/Umbraco.Core/Services/PublishResultType.cs
@@ -97,9 +97,9 @@
FailedPublishContentInvalid = FailedPublish | 8,
///
- /// Cannot republish a document that hasn't been published.
+ /// Cannot publish a document that has no publishing flags or values
///
- FailedPublishNoPublishedValues = FailedPublish | 9, // in ContentService.StrategyCanPublish - fixme weird
+ FailedPublishNothingToPublish = FailedPublish | 9, // in ContentService.StrategyCanPublish - fixme weird
///
/// Some mandatory cultures are missing.
diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs
index 0f0f75edb5..92f0adf9d1 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs
@@ -250,6 +250,90 @@ namespace Umbraco.Tests.Services
Assert.That(hierarchy.All(c => c.Path.StartsWith("-1,-20") == false), Is.True);
}
+ [Test]
+ public void Perform_Scheduled_Publishing()
+ {
+ var langUk = new Language("en-GB") { IsDefault = true };
+ var langFr = new Language("fr-FR");
+
+ ServiceContext.LocalizationService.Save(langFr);
+ ServiceContext.LocalizationService.Save(langUk);
+
+ var ctInvariant = MockedContentTypes.CreateBasicContentType("invariantPage");
+ ServiceContext.ContentTypeService.Save(ctInvariant);
+
+ var ctVariant = MockedContentTypes.CreateBasicContentType("variantPage");
+ ctVariant.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(ctVariant);
+
+ var now = DateTime.Now;
+
+ //10x invariant content, half is scheduled to be published in 5 seconds, the other half is scheduled to be unpublished in 5 seconds
+ var invariant = new List();
+ for (var i = 0; i < 10; i++)
+ {
+ var c = MockedContent.CreateBasicContent(ctInvariant);
+ c.Name = "name" + i;
+ if (i % 2 == 0)
+ {
+ c.ContentSchedule.Add(now.AddSeconds(5), null); //release in 5 seconds
+ var r = ServiceContext.ContentService.Save(c);
+ Assert.IsTrue(r.Success, r.Result.ToString());
+ }
+ else
+ {
+ c.ContentSchedule.Add(null, now.AddSeconds(5)); //expire in 5 seconds
+ var r = ServiceContext.ContentService.SaveAndPublish(c);
+ Assert.IsTrue(r.Success, r.Result.ToString());
+ }
+ invariant.Add(c);
+ }
+
+ //10x variant content, half is scheduled to be published in 5 seconds, the other half is scheduled to be unpublished in 5 seconds
+ var variant = new List();
+ var alternatingCulture = langFr.IsoCode;
+ for (var i = 0; i < 10; i++)
+ {
+ var c = MockedContent.CreateBasicContent(ctVariant);
+ c.SetCultureName("name-uk" + i, langUk.IsoCode);
+ c.SetCultureName("name-fr" + i, langFr.IsoCode);
+
+ if (i % 2 == 0)
+ {
+ c.ContentSchedule.Add(alternatingCulture, now.AddSeconds(5), null); //release in 5 seconds
+ var r = ServiceContext.ContentService.Save(c);
+ Assert.IsTrue(r.Success, r.Result.ToString());
+
+ alternatingCulture = alternatingCulture == langFr.IsoCode ? langUk.IsoCode : langFr.IsoCode;
+ }
+ else
+ {
+ c.ContentSchedule.Add(alternatingCulture, null, now.AddSeconds(5)); //expire in 5 seconds
+ var r = ServiceContext.ContentService.SaveAndPublish(c);
+ Assert.IsTrue(r.Success, r.Result.ToString());
+ }
+ variant.Add(c);
+ }
+
+
+ var runSched = ServiceContext.ContentService.PerformScheduledPublish(
+ now.AddMinutes(1)).ToList(); //lets go way later just to be safe, NOTE: This is NOT based on actual timer so it's safe
+
+ //this is 21 because the test data installed before this test runs has a scheduled item!
+ Assert.AreEqual(21, runSched.Count);
+ Assert.AreEqual(20, runSched.Count(x => x.Success),
+ string.Join(Environment.NewLine, runSched.Select(x => $"{x.Entity.Name} - {x.Result}")));
+
+ Assert.AreEqual(5, runSched.Count(x => x.Result == PublishResultType.SuccessPublish),
+ string.Join(Environment.NewLine, runSched.Select(x => $"{x.Entity.Name} - {x.Result}")));
+ Assert.AreEqual(5, runSched.Count(x => x.Result == PublishResultType.SuccessUnpublish),
+ string.Join(Environment.NewLine, runSched.Select(x => $"{x.Entity.Name} - {x.Result}")));
+ Assert.AreEqual(5, runSched.Count(x => x.Result == PublishResultType.SuccessPublishCulture),
+ string.Join(Environment.NewLine, runSched.Select(x => $"{x.Entity.Name} - {x.Result}")));
+ Assert.AreEqual(5, runSched.Count(x => x.Result == PublishResultType.SuccessUnpublishCulture),
+ string.Join(Environment.NewLine, runSched.Select(x => $"{x.Entity.Name} - {x.Result}")));
+ }
+
[Test]
public void Remove_Scheduled_Publishing_Date()
{