diff --git a/src/Umbraco.Core/Extensions/ContentExtensions.cs b/src/Umbraco.Core/Extensions/ContentExtensions.cs
index daca62926a..b9d1c0b7b4 100644
--- a/src/Umbraco.Core/Extensions/ContentExtensions.cs
+++ b/src/Umbraco.Core/Extensions/ContentExtensions.cs
@@ -141,7 +141,7 @@ namespace Umbraco.Extensions
///
/// Gets the current status of the Content
///
- public static ContentStatus GetStatus(this IContent content, string culture = null)
+ public static ContentStatus GetStatus(this IContent content, ContentScheduleCollection contentSchedule, string culture = null)
{
if (content.Trashed)
return ContentStatus.Trashed;
@@ -151,11 +151,11 @@ namespace Umbraco.Extensions
else if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullException($"{nameof(culture)} cannot be null or empty");
- var expires = content.ContentSchedule.GetSchedule(culture, ContentScheduleAction.Expire);
+ var expires = contentSchedule.GetSchedule(culture, ContentScheduleAction.Expire);
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date))
return ContentStatus.Expired;
- var release = content.ContentSchedule.GetSchedule(culture, ContentScheduleAction.Release);
+ var release = contentSchedule.GetSchedule(culture, ContentScheduleAction.Release);
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now))
return ContentStatus.AwaitingRelease;
diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index 276219aae3..3648f7907e 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -15,7 +15,6 @@ namespace Umbraco.Cms.Core.Models
public class Content : ContentBase, IContent
{
private int? _templateId;
- private ContentScheduleCollection _schedule;
private bool _published;
private PublishedState _publishedState;
private HashSet _editedCultures;
@@ -112,44 +111,6 @@ namespace Umbraco.Cms.Core.Models
PublishedVersionId = 0;
}
- ///
- [DoNotClone]
- public ContentScheduleCollection ContentSchedule
- {
- get
- {
- if (_schedule == null)
- {
- _schedule = new ContentScheduleCollection();
- _schedule.CollectionChanged += ScheduleCollectionChanged;
- }
- return _schedule;
- }
- set
- {
- if (_schedule != null)
- {
- _schedule.ClearCollectionChangedEvents();
- }
-
- SetPropertyValueAndDetectChanges(value, ref _schedule, nameof(ContentSchedule));
- if (_schedule != null)
- {
- _schedule.CollectionChanged += ScheduleCollectionChanged;
- }
- }
- }
-
- ///
- /// Collection changed event handler to ensure the schedule field is set to dirty when the schedule changes
- ///
- ///
- ///
- private void ScheduleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- OnPropertyChanged(nameof(ContentSchedule));
- }
-
///
/// Gets or sets the template used by the Content.
/// This is used to override the default one from the ContentType.
@@ -484,14 +445,6 @@ namespace Umbraco.Cms.Core.Models
clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler
}
- //if properties exist then deal with event bindings
- if (clonedContent._schedule != null)
- {
- clonedContent._schedule.ClearCollectionChangedEvents(); //clear this event handler if any
- clonedContent._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone
- clonedContent._schedule.CollectionChanged += clonedContent.ScheduleCollectionChanged; //re-assign correct event handler
- }
-
clonedContent._currentPublishCultureChanges.updatedCultures = null;
clonedContent._currentPublishCultureChanges.addedCultures = null;
clonedContent._currentPublishCultureChanges.removedCultures = null;
diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs
index 7a3f441e0a..23d5fbc2fd 100644
--- a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
@@ -7,18 +7,23 @@ using Umbraco.Cms.Core.Routing;
namespace Umbraco.Cms.Core.Models.ContentEditing
{
+ public class ContentItemDisplay : ContentItemDisplay { }
+
+ public class ContentItemDisplayWithSchedule : ContentItemDisplay { }
+
///
/// A model representing a content item to be displayed in the back office
///
[DataContract(Name = "content", Namespace = "")]
- public class ContentItemDisplay : INotificationModel, IErrorModel //ListViewAwareContentItemDisplayBase
+ public class ContentItemDisplay : INotificationModel, IErrorModel //ListViewAwareContentItemDisplayBase
+ where TVariant : ContentVariantDisplay
{
public ContentItemDisplay()
{
AllowPreview = true;
Notifications = new List();
Errors = new Dictionary();
- Variants = new List();
+ Variants = new List();
ContentApps = new List();
}
@@ -60,7 +65,7 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
/// If a content item is invariant, this collection will only contain one item, else it will contain all culture variants
///
[DataMember(Name = "variants")]
- public IEnumerable Variants { get; set; }
+ public IEnumerable Variants { get; set; }
[DataMember(Name = "owner")]
public UserProfile Owner { get; set; }
diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs
index b1d53c2059..70e025a865 100644
--- a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -60,12 +60,6 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
[DataMember(Name = "publishDate")]
public DateTime? PublishDate { get; set; }
- [DataMember(Name = "releaseDate")]
- public DateTime? ReleaseDate { get; set; }
-
- [DataMember(Name = "expireDate")]
- public DateTime? ExpireDate { get; set; }
-
///
/// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
///
@@ -76,4 +70,13 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
[ReadOnly(true)]
public List Notifications { get; private set; }
}
+
+ public class ContentVariantScheduleDisplay : ContentVariantDisplay
+ {
+ [DataMember(Name = "releaseDate")]
+ public DateTime? ReleaseDate { get; set; }
+
+ [DataMember(Name = "expireDate")]
+ public DateTime? ExpireDate { get; set; }
+ }
}
diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Core/Models/ContentScheduleCollection.cs
index 34e1dcea3f..f8f6c323f7 100644
--- a/src/Umbraco.Core/Models/ContentScheduleCollection.cs
+++ b/src/Umbraco.Core/Models/ContentScheduleCollection.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
@@ -224,5 +224,19 @@ namespace Umbraco.Cms.Core.Models
return true;
}
+
+ public static ContentScheduleCollection CreateWithEntry(DateTime? release, DateTime? expire)
+ {
+ var schedule = new ContentScheduleCollection();
+ schedule.Add(string.Empty, release, expire);
+ return schedule;
+ }
+
+ public static ContentScheduleCollection CreateWithEntry(string culture, DateTime? release, DateTime? expire)
+ {
+ var schedule = new ContentScheduleCollection();
+ schedule.Add(culture, release, expire);
+ return schedule;
+ }
}
}
diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs
index 516d82b7bb..4951741122 100644
--- a/src/Umbraco.Core/Models/IContent.cs
+++ b/src/Umbraco.Core/Models/IContent.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
namespace Umbraco.Cms.Core.Models
@@ -12,11 +12,6 @@ namespace Umbraco.Cms.Core.Models
///
public interface IContent : IContentBase
{
- ///
- /// Gets or sets the content schedule
- ///
- ContentScheduleCollection ContentSchedule { get; set; }
-
///
/// Gets or sets the template id used to render the content.
///
diff --git a/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs
index 8aaa515dcd..d735757508 100644
--- a/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs
+++ b/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Cms.Core.Mapping;
@@ -19,24 +19,24 @@ namespace Umbraco.Cms.Core.Models.Mapping
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
}
- public IEnumerable Map(IContent source, MapperContext context)
+ public IEnumerable Map(IContent source, MapperContext context) where TVariant : ContentVariantDisplay
{
var variesByCulture = source.ContentType.VariesByCulture();
var variesBySegment = source.ContentType.VariesBySegment();
- IList variants = new List();
+ IList variants = new List();
if (!variesByCulture && !variesBySegment)
{
// this is invariant so just map the IContent instance to ContentVariationDisplay
- var variantDisplay = context.Map(source);
+ var variantDisplay = context.Map(source);
variants.Add(variantDisplay);
}
else if (variesByCulture && !variesBySegment)
{
var languages = GetLanguages(context);
variants = languages
- .Select(language => CreateVariantDisplay(context, source, language, null))
+ .Select(language => CreateVariantDisplay(context, source, language, null))
.ToList();
}
else if (variesBySegment && !variesByCulture)
@@ -44,7 +44,7 @@ namespace Umbraco.Cms.Core.Models.Mapping
// Segment only
var segments = GetSegments(source);
variants = segments
- .Select(segment => CreateVariantDisplay(context, source, null, segment))
+ .Select(segment => CreateVariantDisplay(context, source, null, segment))
.ToList();
}
else
@@ -61,14 +61,16 @@ namespace Umbraco.Cms.Core.Models.Mapping
variants = languages
.SelectMany(language => segments
- .Select(segment => CreateVariantDisplay(context, source, language, segment)))
+ .Select(segment => CreateVariantDisplay(context, source, language, segment)))
.ToList();
}
return SortVariants(variants);
}
- private IList SortVariants(IList variants)
+
+
+ private IList SortVariants(IList variants) where TVariant : ContentVariantDisplay
{
if (variants == null || variants.Count <= 1)
{
@@ -128,12 +130,12 @@ namespace Umbraco.Cms.Core.Models.Mapping
return segments.Distinct();
}
- private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, ContentEditing.Language language, string segment)
+ private TVariant CreateVariantDisplay(MapperContext context, IContent content, ContentEditing.Language language, string segment) where TVariant : ContentVariantDisplay
{
context.SetCulture(language?.IsoCode);
context.SetSegment(segment);
- var variantDisplay = context.Map(content);
+ var variantDisplay = context.Map(content);
variantDisplay.Segment = segment;
variantDisplay.Language = language;
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index e58664fef8..28b1eb8636 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -67,6 +67,20 @@ namespace Umbraco.Cms.Core.Services
///
IContent GetById(Guid key);
+ ///
+ /// Gets publish/unpublish schedule for a content node.
+ ///
+ /// Id of the Content to load schedule for
+ ///
+ ContentScheduleCollection GetContentScheduleByContentId(int contentId);
+
+ ///
+ /// Persists publish/unpublish schedule for a content node.
+ ///
+ ///
+ ///
+ void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule);
+
///
/// Gets documents.
///
@@ -236,7 +250,7 @@ namespace Umbraco.Cms.Core.Services
///
/// Saves a document.
///
- OperationResult Save(IContent content, int userId = Constants.Security.SuperUserId);
+ OperationResult Save(IContent content, int userId = Constants.Security.SuperUserId, ContentScheduleCollection contentSchedule = null);
///
/// Saves documents.
diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs
index 749633a1f3..a451ac0879 100644
--- a/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs
@@ -172,9 +172,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories
return dto;
}
- public static IEnumerable<(ContentSchedule Model, ContentScheduleDto Dto)> BuildScheduleDto(IContent entity, ILanguageRepository languageRepository)
+ public static IEnumerable<(ContentSchedule Model, ContentScheduleDto Dto)> BuildScheduleDto(IContent entity, ContentScheduleCollection contentSchedule, ILanguageRepository languageRepository)
{
- return entity.ContentSchedule.FullSchedule.Select(x =>
+ return contentSchedule.FullSchedule.Select(x =>
(x, new ContentScheduleDto
{
Action = x.Action.ToString(),
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/IDocumentRepository.cs
index 03d5fd12e2..0aebfa28a5 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/IDocumentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/IDocumentRepository.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
@@ -7,6 +7,20 @@ namespace Umbraco.Cms.Core.Persistence.Repositories
{
public interface IDocumentRepository : IContentRepository, IReadRepository
{
+ ///
+ /// Gets publish/unpublish schedule for a content node.
+ ///
+ ///
+ ///
+ ContentScheduleCollection GetContentSchedule(int contentId);
+
+ ///
+ /// Persists publish/unpublish schedule for a content node.
+ ///
+ ///
+ ///
+ void PersistContentSchedule(IContent content, ContentScheduleCollection schedule);
+
///
/// Clears the publishing schedule for all entries having an a date before (lower than, or equal to) a specified date.
///
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
index 22e2480bad..af98cd7f10 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -294,7 +294,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
return MapDtosToContent(Database.Fetch(sql), true,
// load bare minimum, need variants though since this is used to rollback with variants
- false, false, false, true).Skip(skip).Take(take);
+ false, false, true).Skip(skip).Take(take);
}
public override IContent GetVersion(int versionId)
@@ -477,9 +477,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
Database.Insert(dto);
- //insert the schedule
- PersistContentSchedule(entity, false);
-
// persist the variations
if (entity.ContentType.VariesByCulture())
{
@@ -735,12 +732,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
Database.Update(dto);
- //update the schedule
- if (entity.IsPropertyDirty(nameof(entity.ContentSchedule)))
- {
- PersistContentSchedule(entity, true);
- }
-
// if entity is publishing, update tags, else leave tags there
// means that implicitly unpublished, or trashed, entities *still* have tags in db
if (entity.PublishedState == PublishedState.Publishing)
@@ -797,19 +788,27 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
//}
}
- private void PersistContentSchedule(IContent content, bool update)
+ ///
+ public void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule)
{
- var schedules = ContentBaseFactory.BuildScheduleDto(content, LanguageRepository).ToList();
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+
+ if (contentSchedule == null)
+ {
+ throw new ArgumentNullException(nameof(contentSchedule));
+ }
+
+ var schedules = ContentBaseFactory.BuildScheduleDto(content, contentSchedule, LanguageRepository).ToList();
//remove any that no longer exist
- if (update)
- {
- var ids = schedules.Where(x => x.Model.Id != Guid.Empty).Select(x => x.Model.Id).Distinct();
- Database.Execute(Sql()
- .Delete()
- .Where(x => x.NodeId == content.Id)
- .WhereNotIn(x => x.Id, ids));
- }
+ var ids = schedules.Where(x => x.Model.Id != Guid.Empty).Select(x => x.Model.Id).Distinct();
+ Database.Execute(Sql()
+ .Delete()
+ .Where(x => x.NodeId == content.Id)
+ .WhereNotIn(x => x.Id, ids));
//add/update the rest
foreach (var schedule in schedules)
@@ -1208,7 +1207,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
bool withCache = false,
bool loadProperties = true,
bool loadTemplates = true,
- bool loadSchedule = true,
bool loadVariants = true)
{
var temps = new List>();
@@ -1283,8 +1281,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
properties = GetPropertyCollections(temps);
}
- var schedule = GetContentSchedule(temps.Select(x => x.Content.Id).ToArray());
-
// assign templates and properties
foreach (var temp in temps)
{
@@ -1306,14 +1302,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
else
throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'.");
}
-
- if (loadSchedule)
- {
- // load in the schedule
- if (schedule.TryGetValue(temp.Content.Id, out var s))
- temp.Content.ContentSchedule = s;
- }
-
}
if (loadVariants)
@@ -1371,11 +1359,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
SetVariations(content, contentVariations, documentVariations);
}
- //load in the schedule
- var schedule = GetContentSchedule(dto.NodeId);
- if (schedule.TryGetValue(dto.NodeId, out var s))
- content.ContentSchedule = s;
-
// reset dirty initial properties (U4-1946)
content.ResetDirtyProperties(false);
return content;
@@ -1386,21 +1369,19 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
}
}
- private IDictionary GetContentSchedule(params int[] contentIds)
+ ///
+ public ContentScheduleCollection GetContentSchedule(int contentId)
{
- var result = new Dictionary();
+ var result = new ContentScheduleCollection();
- var scheduleDtos = Database.FetchByGroups(contentIds, 2000, batch => Sql()
+ var scheduleDtos = Database.Fetch(Sql()
.Select()
.From()
- .WhereIn(x => x.NodeId, batch));
+ .Where(x => x.NodeId == contentId ));
foreach (var scheduleDto in scheduleDtos)
{
- if (!result.TryGetValue(scheduleDto.NodeId, out var col))
- col = result[scheduleDto.NodeId] = new ContentScheduleCollection();
-
- col.Add(new ContentSchedule(scheduleDto.Id,
+ result.Add(new ContentSchedule(scheduleDto.Id,
LanguageRepository.GetIsoCodeById(scheduleDto.LanguageId) ?? string.Empty,
scheduleDto.Date,
scheduleDto.Action == ContentScheduleAction.Release.ToString()
diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs
index 21c365dd8e..2182531ff9 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs
@@ -374,6 +374,26 @@ namespace Umbraco.Cms.Core.Services.Implement
}
}
+ ///
+ public ContentScheduleCollection GetContentScheduleByContentId(int contentId)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ scope.ReadLock(Cms.Core.Constants.Locks.ContentTree);
+ return _documentRepository.GetContentSchedule(contentId);
+ }
+ }
+
+ ///
+ public void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ scope.WriteLock(Cms.Core.Constants.Locks.ContentTree);
+ _documentRepository.PersistContentSchedule(content, contentSchedule);
+ }
+ }
+
///
///
///
@@ -757,7 +777,7 @@ namespace Umbraco.Cms.Core.Services.Implement
#region Save, Publish, Unpublish
///
- public OperationResult Save(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId)
+ public OperationResult Save(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId, ContentScheduleCollection contentSchedule = null)
{
PublishedState publishedState = content.PublishedState;
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
@@ -801,6 +821,11 @@ namespace Umbraco.Cms.Core.Services.Implement
_documentRepository.Save(content);
+ if (contentSchedule != null)
+ {
+ _documentRepository.PersistContentSchedule(content, contentSchedule);
+ }
+
scope.Notifications.Publish(new ContentSavedNotification(content, eventMessages).WithStateFrom(savingNotification));
// TODO: we had code here to FORCE that this event can never be suppressed. But that just doesn't make a ton of sense?!
@@ -1431,10 +1456,11 @@ namespace Umbraco.Cms.Core.Services.Implement
foreach (var d in _documentRepository.GetContentForExpiration(date))
{
+ ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(d.Id);
if (d.ContentType.VariesByCulture())
{
//find which cultures have pending schedules
- var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleAction.Expire, date)
+ var pendingCultures = contentSchedule.GetPending(ContentScheduleAction.Expire, date)
.Select(x => x.Culture)
.Distinct()
.ToList();
@@ -1452,11 +1478,12 @@ namespace Umbraco.Cms.Core.Services.Implement
foreach (var c in pendingCultures)
{
//Clear this schedule for this culture
- d.ContentSchedule.Clear(c, ContentScheduleAction.Expire, date);
+ contentSchedule.Clear(c, ContentScheduleAction.Expire, date);
//set the culture to be published
d.UnpublishCulture(c);
}
+ _documentRepository.PersistContentSchedule(d, contentSchedule);
var result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId);
if (result.Success == false)
_logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
@@ -1465,8 +1492,9 @@ namespace Umbraco.Cms.Core.Services.Implement
}
else
{
- //Clear this schedule
- d.ContentSchedule.Clear(ContentScheduleAction.Expire, date);
+ //Clear this schedule for this culture
+ contentSchedule.Clear(ContentScheduleAction.Expire, date);
+ _documentRepository.PersistContentSchedule(d, contentSchedule);
var result = Unpublish(d, userId: d.WriterId);
if (result.Success == false)
_logger.LogError(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
@@ -1492,10 +1520,11 @@ namespace Umbraco.Cms.Core.Services.Implement
foreach (var d in _documentRepository.GetContentForRelease(date))
{
+ ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(d.Id);
if (d.ContentType.VariesByCulture())
{
//find which cultures have pending schedules
- var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleAction.Release, date)
+ var pendingCultures = contentSchedule.GetPending(ContentScheduleAction.Release, date)
.Select(x => x.Culture)
.Distinct()
.ToList();
@@ -1514,9 +1543,10 @@ namespace Umbraco.Cms.Core.Services.Implement
foreach (var culture in pendingCultures)
{
//Clear this schedule for this culture
- d.ContentSchedule.Clear(culture, ContentScheduleAction.Release, date);
+ contentSchedule.Clear(culture, ContentScheduleAction.Release, date);
- if (d.Trashed) continue; // won't publish
+ if (d.Trashed)
+ continue; // won't publish
//publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
IProperty[] invalidProperties = null;
@@ -1527,7 +1557,8 @@ namespace Umbraco.Cms.Core.Services.Implement
d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias)));
publishing &= tryPublish; //set the culture to be published
- if (!publishing) continue; // move to next document
+ if (!publishing)
+ continue; // move to next document
}
PublishResult result;
@@ -1537,7 +1568,11 @@ namespace Umbraco.Cms.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
+ {
+ _documentRepository.PersistContentSchedule(d, contentSchedule);
result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId);
+ }
+
if (result.Success == false)
_logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
@@ -1547,11 +1582,19 @@ namespace Umbraco.Cms.Core.Services.Implement
else
{
//Clear this schedule
- d.ContentSchedule.Clear(ContentScheduleAction.Release, date);
+ contentSchedule.Clear(ContentScheduleAction.Release, date);
- var result = d.Trashed
- ? new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d)
- : SaveAndPublish(d, userId: d.WriterId);
+ PublishResult result = null;
+
+ if (d.Trashed)
+ {
+ result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d);
+ }
+ else
+ {
+ _documentRepository.PersistContentSchedule(d, contentSchedule);
+ result = SaveAndPublish(d, userId: d.WriterId);
+ }
if (result.Success == false)
_logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
@@ -2638,12 +2681,13 @@ namespace Umbraco.Cms.Core.Services.Implement
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
+ ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(content.Id);
//loop over each culture publishing - or string.Empty for invariant
foreach (var culture in culturesPublishing ?? (new[] { string.Empty }))
{
// ensure that the document status is correct
// note: culture will be string.Empty for invariant
- switch (content.GetStatus(culture))
+ switch (content.GetStatus(contentSchedule, culture))
{
case ContentStatus.Expired:
if (!variesByCulture)
@@ -2762,20 +2806,18 @@ namespace Umbraco.Cms.Core.Services.Implement
{
var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
- //TODO: What is this check?? we just created this attempt and of course it is Success?!
- if (attempt.Success == false)
- return attempt;
-
// if the document has any release dates set to before now,
// they should be removed so they don't interrupt an unpublish
// otherwise it would remain released == published
- var pastReleases = content.ContentSchedule.GetPending(ContentScheduleAction.Expire, DateTime.Now);
+ var contentSchedule = _documentRepository.GetContentSchedule(content.Id);
+ var pastReleases = contentSchedule.GetPending(ContentScheduleAction.Expire, DateTime.Now);
foreach (var p in pastReleases)
- content.ContentSchedule.Remove(p);
+ contentSchedule.Remove(p);
if (pastReleases.Count > 0)
_logger.LogInformation("Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.", content.Name, content.Id);
+ _documentRepository.PersistContentSchedule(content, contentSchedule);
// change state to unpublishing
content.PublishedState = PublishedState.Unpublishing;
diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
index 70678545d9..c051bde3c9 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
@@ -321,15 +321,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
[OutgoingEditorModelEvent]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
- public ActionResult GetById(int id)
+ public ActionResult GetById(int id)
{
var foundContent = GetObjectFromRequest(() => _contentService.GetById(id));
if (foundContent == null)
{
return HandleContentNotFound(id);
}
- var content = MapToDisplay(foundContent);
- return content;
+
+ return MapToDisplayWithSchedule(foundContent);
}
///
@@ -339,16 +339,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
[OutgoingEditorModelEvent]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
- public ActionResult GetById(Guid id)
+ public ActionResult GetById(Guid id)
{
var foundContent = GetObjectFromRequest(() => _contentService.GetById(id));
if (foundContent == null)
{
return HandleContentNotFound(id);
}
-
- var content = MapToDisplay(foundContent);
- return content;
+ return MapToDisplayWithSchedule(foundContent);
}
///
@@ -358,7 +356,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
[OutgoingEditorModelEvent]
[Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)]
- public ActionResult GetById(Udi id)
+ public ActionResult GetById(Udi id)
{
var guidUdi = id as GuidUdi;
if (guidUdi != null)
@@ -710,11 +708,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
[FileUploadCleanupFilter]
[ContentSaveValidation]
- public async Task> PostSaveBlueprint([ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem)
+ public async Task>> PostSaveBlueprint([ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem)
{
var contentItemDisplay = await PostSaveInternal(
contentItem,
- content =>
+ (content, _) =>
{
if (!EnsureUniqueName(content.Name, content, "Name"))
{
@@ -742,17 +740,18 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[FileUploadCleanupFilter]
[ContentSaveValidation]
[OutgoingEditorModelEvent]
- public async Task> PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem)
+ public async Task>> PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem)
{
var contentItemDisplay = await PostSaveInternal(
contentItem,
- content => _contentService.Save(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id),
- MapToDisplay);
+ (content, contentSchedule) => _contentService.Save(content, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id, contentSchedule),
+ MapToDisplayWithSchedule);
return contentItemDisplay;
}
- private async Task> PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func mapToDisplay)
+ private async Task>> PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func> mapToDisplay)
+ where TVariant : ContentVariantDisplay
{
// Recent versions of IE/Edge may send in the full client side file path instead of just the file name.
// To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all
@@ -832,17 +831,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
case ContentSaveAction.Save:
case ContentSaveAction.SaveNew:
- SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentSavedText", "editVariantSavedText", cultureForInvariantErrors, out wasCancelled);
+ SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentSavedText", "editVariantSavedText", cultureForInvariantErrors, null, out wasCancelled);
break;
case ContentSaveAction.Schedule:
case ContentSaveAction.ScheduleNew:
-
- if (!SaveSchedule(contentItem, globalNotifications))
+ ContentScheduleCollection contentSchedule = _contentService.GetContentScheduleByContentId(contentItem.Id);
+ if (!SaveSchedule(contentItem, contentSchedule, globalNotifications))
{
wasCancelled = false;
break;
}
- SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentScheduledSavedText", "editVariantSavedText", cultureForInvariantErrors, out wasCancelled);
+ SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentScheduledSavedText", "editVariantSavedText", cultureForInvariantErrors, contentSchedule, out wasCancelled);
break;
case ContentSaveAction.SendPublish:
@@ -1047,12 +1046,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
/// Method is used for normal Saving and Scheduled Publishing
///
- private void SaveAndNotify(ContentItemSave contentItem, Func saveMethod, int variantCount,
+ private void SaveAndNotify(ContentItemSave contentItem, Func saveMethod, int variantCount,
Dictionary notifications, SimpleNotificationModel globalNotifications,
- string invariantSavedLocalizationAlias, string variantSavedLocalizationAlias, string cultureForInvariantErrors,
+ string invariantSavedLocalizationAlias, string variantSavedLocalizationAlias, string cultureForInvariantErrors, ContentScheduleCollection contentSchedule,
out bool wasCancelled)
{
- var saveResult = saveMethod(contentItem.PersistedContent);
+ var saveResult = saveMethod(contentItem.PersistedContent, contentSchedule);
wasCancelled = saveResult.Success == false && saveResult.Result == OperationResultType.FailedCancelledByEvent;
if (saveResult.Success)
{
@@ -1087,20 +1086,20 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
///
///
- private bool SaveSchedule(ContentItemSave contentItem, SimpleNotificationModel globalNotifications)
+ private bool SaveSchedule(ContentItemSave contentItem, ContentScheduleCollection contentSchedule, SimpleNotificationModel globalNotifications)
{
if (!contentItem.PersistedContent.ContentType.VariesByCulture())
- return SaveScheduleInvariant(contentItem, globalNotifications);
+ return SaveScheduleInvariant(contentItem, contentSchedule, globalNotifications);
else
- return SaveScheduleVariant(contentItem);
+ return SaveScheduleVariant(contentItem, contentSchedule);
}
- private bool SaveScheduleInvariant(ContentItemSave contentItem, SimpleNotificationModel globalNotifications)
+ private bool SaveScheduleInvariant(ContentItemSave contentItem, ContentScheduleCollection contentSchedule, SimpleNotificationModel globalNotifications)
{
var variant = contentItem.Variants.First();
- var currRelease = contentItem.PersistedContent.ContentSchedule.GetSchedule(ContentScheduleAction.Release).ToList();
- var currExpire = contentItem.PersistedContent.ContentSchedule.GetSchedule(ContentScheduleAction.Expire).ToList();
+ var currRelease = contentSchedule.GetSchedule(ContentScheduleAction.Release).ToList();
+ var currExpire = contentSchedule.GetSchedule(ContentScheduleAction.Expire).ToList();
//Do all validation of data first
@@ -1137,45 +1136,41 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//remove any existing release dates so we can replace it
//if there is a release date in the request or if there was previously a release and the request value is null then we are clearing the schedule
if (variant.ReleaseDate.HasValue || currRelease.Count > 0)
- contentItem.PersistedContent.ContentSchedule.Clear(ContentScheduleAction.Release);
+ contentSchedule.Clear(ContentScheduleAction.Release);
//remove any existing expire dates so we can replace it
//if there is an expiry date in the request or if there was a previous expiry and the request value is null then we are clearing the schedule
if (variant.ExpireDate.HasValue || currExpire.Count > 0)
- contentItem.PersistedContent.ContentSchedule.Clear(ContentScheduleAction.Expire);
+ contentSchedule.Clear(ContentScheduleAction.Expire);
//add the new schedule
- contentItem.PersistedContent.ContentSchedule.Add(variant.ReleaseDate, variant.ExpireDate);
+ contentSchedule.Add(variant.ReleaseDate, variant.ExpireDate);
return true;
}
- private bool SaveScheduleVariant(ContentItemSave contentItem)
+ private bool SaveScheduleVariant(ContentItemSave contentItem, ContentScheduleCollection contentSchedule)
{
//All variants in this collection should have a culture if we get here but we'll double check and filter here)
var cultureVariants = contentItem.Variants.Where(x => !x.Culture.IsNullOrWhiteSpace()).ToList();
var mandatoryCultures = _allLangs.Value.Values.Where(x => x.IsMandatory).Select(x => x.IsoCode).ToList();
- //Make a copy of the current schedule and apply updates to it
-
- var schedCopy = (ContentScheduleCollection)contentItem.PersistedContent.ContentSchedule.DeepClone();
-
foreach (var variant in cultureVariants.Where(x => x.Save))
{
- var currRelease = schedCopy.GetSchedule(variant.Culture, ContentScheduleAction.Release).ToList();
- var currExpire = schedCopy.GetSchedule(variant.Culture, ContentScheduleAction.Expire).ToList();
+ var currRelease = contentSchedule.GetSchedule(variant.Culture, ContentScheduleAction.Release).ToList();
+ var currExpire = contentSchedule.GetSchedule(variant.Culture, ContentScheduleAction.Expire).ToList();
//remove any existing release dates so we can replace it
//if there is a release date in the request or if there was previously a release and the request value is null then we are clearing the schedule
if (variant.ReleaseDate.HasValue || currRelease.Count > 0)
- schedCopy.Clear(variant.Culture, ContentScheduleAction.Release);
+ contentSchedule.Clear(variant.Culture, ContentScheduleAction.Release);
//remove any existing expire dates so we can replace it
//if there is an expiry date in the request or if there was a previous expiry and the request value is null then we are clearing the schedule
if (variant.ExpireDate.HasValue || currExpire.Count > 0)
- schedCopy.Clear(variant.Culture, ContentScheduleAction.Expire);
+ contentSchedule.Clear(variant.Culture, ContentScheduleAction.Expire);
//add the new schedule
- schedCopy.Add(variant.Culture, variant.ReleaseDate, variant.ExpireDate);
+ contentSchedule.Add(variant.Culture, variant.ReleaseDate, variant.ExpireDate);
}
//now validate the new schedule to make sure it passes all of the rules
@@ -1185,7 +1180,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//create lists of mandatory/non-mandatory states
var mandatoryVariants = new List<(string culture, bool isPublished, List releaseDates)>();
var nonMandatoryVariants = new List<(string culture, bool isPublished, List releaseDates)>();
- foreach (var groupedSched in schedCopy.FullSchedule.GroupBy(x => x.Culture))
+ foreach (var groupedSched in contentSchedule.FullSchedule.GroupBy(x => x.Culture))
{
var isPublished = contentItem.PersistedContent.Published && contentItem.PersistedContent.IsCulturePublished(groupedSched.Key);
var releaseDates = groupedSched.Where(x => x.Action == ContentScheduleAction.Release).Select(x => x.Date).ToList();
@@ -1218,7 +1213,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
}
- if (!isValid) return false;
+ if (!isValid)
+ return false;
//now we can validate the more basic rules for individual variants
foreach (var variant in cultureVariants.Where(x => x.ReleaseDate.HasValue || x.ExpireDate.HasValue))
@@ -1248,11 +1244,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
}
- if (!isValid) return false;
+ if (!isValid)
+ return false;
-
- //now that we are validated, we can assign the copied schedule back to the model
- contentItem.PersistedContent.ContentSchedule = schedCopy;
return true;
}
@@ -1855,7 +1849,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// The content and variants to unpublish
///
[OutgoingEditorModelEvent]
- public async Task> PostUnpublish(UnpublishContent model)
+ public async Task> PostUnpublish(UnpublishContent model)
{
var foundContent = _contentService.GetById(model.Id);
@@ -1878,7 +1872,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//this means that the entire content item will be unpublished
var unpublishResult = _contentService.Unpublish(foundContent, userId: _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
- var content = MapToDisplay(foundContent);
+ var content = MapToDisplayWithSchedule(foundContent);
if (!unpublishResult.Success)
{
@@ -1908,7 +1902,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
}
- var content = MapToDisplay(foundContent);
+ var content = MapToDisplayWithSchedule(foundContent);
//check for this status and return the correct message
if (results.Any(x => x.Value.Result == PublishResultType.SuccessUnpublishMandatoryCulture))
@@ -2096,7 +2090,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
/// This is required to wire up the validation in the save/publish dialog
///
- private void HandleInvalidModelState(ContentItemDisplay display, string cultureForInvariantErrors)
+ private void HandleInvalidModelState(ContentItemDisplay display, string cultureForInvariantErrors)
+ where TVariant : ContentVariantDisplay
{
if (!ModelState.IsValid && display.Variants.Count() > 1)
{
@@ -2454,6 +2449,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
context.Items["CurrentUser"] = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
});
+ private ContentItemDisplayWithSchedule MapToDisplayWithSchedule(IContent content)
+ {
+ ContentItemDisplayWithSchedule display = _umbracoMapper.Map(content, context =>
+ {
+ context.Items["CurrentUser"] = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
+ context.Items["Schedule"] = _contentService.GetContentScheduleByContentId(content.Id);
+ });
+ display.AllowPreview = display.AllowPreview && content.Trashed == false && content.ContentType.IsElement == false;
+ return display;
+ }
+
///
/// Used to map an instance to a and ensuring AllowPreview is set correctly.
/// Also allows you to pass in an action for the mapper context where you can pass additional information on to the mapper.
@@ -2463,7 +2469,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
private ContentItemDisplay MapToDisplay(IContent content, Action contextOptions)
{
- var display = _umbracoMapper.Map(content, contextOptions);
+ ContentItemDisplay display = _umbracoMapper.Map(content, contextOptions);
display.AllowPreview = display.AllowPreview && content.Trashed == false && content.ContentType.IsElement == false;
return display;
}
diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs
index e234fa1115..50dd66ea19 100644
--- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs
+++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs
@@ -99,10 +99,14 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping
public void DefineMaps(IUmbracoMapper mapper)
{
- mapper.Define((source, context) => new ContentPropertyCollectionDto(), Map);
- mapper.Define((source, context) => new ContentItemDisplay(), Map);
- mapper.Define((source, context) => new ContentVariantDisplay(), Map);
mapper.Define>((source, context) => new ContentItemBasic(), Map);
+ mapper.Define((source, context) => new ContentPropertyCollectionDto(), Map);
+
+ mapper.Define((source, context) => new ContentItemDisplay(), Map);
+ mapper.Define((source, context) => new ContentItemDisplayWithSchedule(), Map);
+
+ mapper.Define((source, context) => new ContentVariantDisplay(), Map);
+ mapper.Define((source, context) => new ContentVariantScheduleDisplay(), Map);
}
// Umbraco.Code.MapAll
@@ -112,7 +116,7 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping
}
// Umbraco.Code.MapAll -AllowPreview -Errors -PersistedContent
- private void Map(IContent source, ContentItemDisplay target, MapperContext context)
+ private void Map(IContent source, ContentItemDisplay target, MapperContext context) where TVariant : ContentVariantDisplay
{
// Both GetActions and DetermineIsChildOfListView use parent, so get it once here
// Parent might already be in context, so check there before using content service
@@ -154,7 +158,7 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping
target.UpdateDate = source.UpdateDate;
target.Updater = _commonMapper.GetCreator(source, context);
target.Urls = GetUrls(source);
- target.Variants = _contentVariantMapper.Map(source, context);
+ target.Variants = _contentVariantMapper.Map(source, context);
target.ContentDto = new ContentPropertyCollectionDto();
target.ContentDto.Properties = context.MapEnumerable(source.Properties);
@@ -164,15 +168,20 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping
private void Map(IContent source, ContentVariantDisplay target, MapperContext context)
{
target.CreateDate = source.CreateDate;
- target.ExpireDate = GetScheduledDate(source, ContentScheduleAction.Expire, context);
target.Name = source.Name;
target.PublishDate = source.PublishDate;
- target.ReleaseDate = GetScheduledDate(source, ContentScheduleAction.Release, context);
target.State = _stateMapper.Map(source, context);
target.Tabs = _tabsAndPropertiesMapper.Map(source, context);
target.UpdateDate = source.UpdateDate;
}
+ private void Map(IContent source, ContentVariantScheduleDisplay target, MapperContext context)
+ {
+ Map(source, (ContentVariantDisplay)target, context);
+ target.ReleaseDate = GetScheduledDate(source, ContentScheduleAction.Release, context);
+ target.ExpireDate = GetScheduledDate(source, ContentScheduleAction.Expire, context);
+ }
+
// Umbraco.Code.MapAll -Alias
private void Map(IContent source, ContentItemBasic target, MapperContext context)
{
@@ -354,8 +363,15 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping
private DateTime? GetScheduledDate(IContent source, ContentScheduleAction action, MapperContext context)
{
+ _ = context.Items.TryGetValue("Schedule", out var untypedSchedule);
+
+ if (untypedSchedule is not ContentScheduleCollection scheduleCollection)
+ {
+ throw new ApplicationException("GetScheduledDate requires a ContentScheduleCollection in the MapperContext for Key: Schedule");
+ }
+
var culture = context.GetCulture() ?? string.Empty;
- var schedule = source.ContentSchedule.GetSchedule(culture, action);
+ IEnumerable schedule = scheduleCollection.GetSchedule(culture, action);
return schedule.FirstOrDefault()?.Date; // take the first, it's ordered by date
}
diff --git a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs
index 2f217ea66b..d04d3a7298 100644
--- a/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs
+++ b/tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
@@ -39,8 +39,8 @@ namespace Umbraco.Cms.Tests.Integration.Testing
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1054
Subpage = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 1", Textpage.Id);
- Subpage.ContentSchedule.Add(DateTime.Now.AddMinutes(-5), null);
- ContentService.Save(Subpage, 0);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
+ ContentService.Save(Subpage, 0, contentSchedule);
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1055
Subpage2 = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 2", Textpage.Id);
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs
index ad189cc02a..a5086e7531 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs
@@ -224,14 +224,17 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
c.Name = "name" + i;
if (i % 2 == 0)
{
- c.ContentSchedule.Add(now.AddSeconds(5), null); // release in 5 seconds
- OperationResult r = ContentService.Save(c);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(now.AddSeconds(5), null); // release in 5 seconds
+ OperationResult r = ContentService.Save(c, contentSchedule: contentSchedule);
Assert.IsTrue(r.Success, r.Result.ToString());
}
else
{
- c.ContentSchedule.Add(null, now.AddSeconds(5)); // expire in 5 seconds
PublishResult r = ContentService.SaveAndPublish(c);
+
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, now.AddSeconds(5)); // expire in 5 seconds
+ ContentService.PersistContentSchedule(c, contentSchedule);
+
Assert.IsTrue(r.Success, r.Result.ToString());
}
@@ -249,16 +252,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
if (i % 2 == 0)
{
- c.ContentSchedule.Add(alternatingCulture, now.AddSeconds(5), null); // release in 5 seconds
- OperationResult r = ContentService.Save(c);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(alternatingCulture, now.AddSeconds(5), null); // release in 5 seconds
+ OperationResult r = ContentService.Save(c, contentSchedule: contentSchedule);
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
PublishResult r = ContentService.SaveAndPublish(c);
+
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(alternatingCulture, null, now.AddSeconds(5)); // expire in 5 seconds
+ ContentService.PersistContentSchedule(c, contentSchedule);
+
Assert.IsTrue(r.Success, r.Result.ToString());
}
@@ -307,20 +313,20 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
// Act
IContent content = ContentService.CreateAndSave("Test", Constants.System.Root, "umbTextpage", Constants.Security.SuperUserId);
- content.ContentSchedule.Add(null, DateTime.Now.AddHours(2));
- ContentService.Save(content, Constants.Security.SuperUserId);
- Assert.AreEqual(1, content.ContentSchedule.FullSchedule.Count);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddHours(2));
+ ContentService.Save(content, Constants.Security.SuperUserId, contentSchedule);
+ Assert.AreEqual(1, contentSchedule.FullSchedule.Count);
- content = ContentService.GetById(content.Id);
- IReadOnlyList sched = content.ContentSchedule.FullSchedule;
+ contentSchedule = ContentService.GetContentScheduleByContentId(content.Id);
+ IReadOnlyList sched = contentSchedule.FullSchedule;
Assert.AreEqual(1, sched.Count);
Assert.AreEqual(1, sched.Count(x => x.Culture == string.Empty));
- content.ContentSchedule.Clear(ContentScheduleAction.Expire);
- ContentService.Save(content, Constants.Security.SuperUserId);
+ contentSchedule.Clear(ContentScheduleAction.Expire);
+ ContentService.Save(content, Constants.Security.SuperUserId, contentSchedule);
// Assert
- content = ContentService.GetById(content.Id);
- sched = content.ContentSchedule.FullSchedule;
+ contentSchedule = ContentService.GetContentScheduleByContentId(content.Id);
+ sched = contentSchedule.FullSchedule;
Assert.AreEqual(0, sched.Count);
Assert.IsTrue(ContentService.SaveAndPublish(content).Success);
}
@@ -646,7 +652,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
IContent root = ContentService.GetById(Textpage.Id);
ContentService.SaveAndPublish(root);
IContent content = ContentService.GetById(Subpage.Id);
- content.ContentSchedule.Add(null, DateTime.Now.AddSeconds(1));
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddSeconds(1));
+ ContentService.PersistContentSchedule(content, contentSchedule);
ContentService.SaveAndPublish(content);
// Act
@@ -1292,8 +1299,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
{
// Arrange
IContent content = ContentService.GetById(Subpage.Id); // This Content expired 5min ago
- content.ContentSchedule.Add(null, DateTime.Now.AddMinutes(-5));
- ContentService.Save(content);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddMinutes(-5));
+ ContentService.Save(content, contentSchedule: contentSchedule);
IContent parent = ContentService.GetById(Textpage.Id);
PublishResult parentPublished = ContentService.SaveAndPublish(parent, userId: Constants.Security.SuperUserId); // Publish root Home node to enable publishing of 'Subpage.Id'
@@ -1317,8 +1324,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
Content content = ContentBuilder.CreateBasicContent(contentType);
content.SetCultureName("Hello", "en-US");
- content.ContentSchedule.Add("en-US", null, DateTime.Now.AddMinutes(-5));
- ContentService.Save(content);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", null, DateTime.Now.AddMinutes(-5));
+ ContentService.Save(content, contentSchedule: contentSchedule);
PublishResult published = ContentService.SaveAndPublish(content, "en-US", Constants.Security.SuperUserId);
@@ -1332,8 +1339,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
{
// Arrange
IContent content = ContentService.GetById(Subpage.Id);
- content.ContentSchedule.Add(DateTime.Now.AddHours(2), null);
- ContentService.Save(content, Constants.Security.SuperUserId);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null);
+ ContentService.Save(content, Constants.Security.SuperUserId, contentSchedule);
IContent parent = ContentService.GetById(Textpage.Id);
PublishResult parentPublished = ContentService.SaveAndPublish(parent, userId: Constants.Security.SuperUserId); // Publish root Home node to enable publishing of 'Subpage.Id'
@@ -1380,8 +1387,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
contentService.SaveAndPublish(content);
content.Properties[0].SetValue("Foo", culture: string.Empty);
- content.ContentSchedule.Add(DateTime.Now.AddHours(2), null);
contentService.Save(content);
+ contentService.PersistContentSchedule(content, ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null));
// Act
var result = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId);
@@ -1430,7 +1437,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
contentService.SaveAndPublish(content);
- content.ContentSchedule.Add(DateTime.Now.AddHours(2), null);
+ contentService.PersistContentSchedule(content, ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null));
contentService.Save(content);
// Act
@@ -1458,8 +1465,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
Content content = ContentBuilder.CreateBasicContent(contentType);
content.SetCultureName("Hello", "en-US");
- content.ContentSchedule.Add("en-US", DateTime.Now.AddHours(2), null);
- ContentService.Save(content);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", DateTime.Now.AddHours(2), null);
+ ContentService.Save(content, contentSchedule: contentSchedule);
PublishResult published = ContentService.SaveAndPublish(content, "en-US", Constants.Security.SuperUserId);
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs
index ae66f65b96..4a8a933cce 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
@@ -809,8 +809,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1054
_subpage = ContentBuilder.CreateSimpleContent(_contentType, "Text Page 1", _textpage.Id);
- _subpage.ContentSchedule.Add(DateTime.Now.AddMinutes(-5), null);
- ContentService.Save(_subpage, 0);
+ var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
+ ContentService.Save(_subpage, 0, contentSchedule);
// Create and Save Content "Text Page 2" based on "umbTextpage" -> 1055
_subpage2 = ContentBuilder.CreateSimpleContent(_contentType, "Text Page 2", _textpage.Id);
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs
index 718559af39..e2281c9585 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs
@@ -253,8 +253,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
content.Key = Guid.NewGuid();
content.Level = 3;
content.Path = "-1,4,10";
- content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1));
- //// content.ChangePublishedState(PublishedState.Published);
content.SortOrder = 5;
content.TemplateId = 88;
content.Trashed = false;
@@ -316,7 +314,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
content.Key = Guid.NewGuid();
content.Level = 3;
content.Path = "-1,4,10";
- content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1));
content.SortOrder = 5;
content.TemplateId = 88;
content.Trashed = false;
@@ -338,7 +335,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
Assert.AreEqual(clone.Key, content.Key);
Assert.AreEqual(clone.Level, content.Level);
Assert.AreEqual(clone.Path, content.Path);
- Assert.IsTrue(clone.ContentSchedule.Equals(content.ContentSchedule));
Assert.AreEqual(clone.Published, content.Published);
Assert.AreEqual(clone.PublishedState, content.PublishedState);
Assert.AreEqual(clone.SortOrder, content.SortOrder);
@@ -421,7 +417,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
content.Id = 10;
content.CreateDate = DateTime.Now;
content.CreatorId = 22;
- content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1));
content.Key = Guid.NewGuid();
content.Level = 3;
content.Path = "-1,4,10";
@@ -442,7 +437,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Key)));
Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Level)));
Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Path)));
- Assert.IsTrue(content.WasPropertyDirty(nameof(Content.ContentSchedule)));
Assert.IsTrue(content.WasPropertyDirty(nameof(Content.SortOrder)));
Assert.IsTrue(content.WasPropertyDirty(nameof(Content.TemplateId)));
Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Trashed)));
@@ -492,8 +486,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Models
content.Key = Guid.NewGuid();
content.Level = 3;
content.Path = "-1,4,10";
- content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1));
- //// content.ChangePublishedState(PublishedState.Publishing);
content.SortOrder = 5;
content.TemplateId = 88;
content.Trashed = false;