diff --git a/build/templates/UmbracoProject/appsettings.json b/build/templates/UmbracoProject/appsettings.json
index feb6b07d9531..d282abc86f1a 100644
--- a/build/templates/UmbracoProject/appsettings.json
+++ b/build/templates/UmbracoProject/appsettings.json
@@ -29,6 +29,11 @@
//#endif
"Hosting": {
"Debug": false
+ },
+ "Content": {
+ "ContentVersionCleanupPolicy": {
+ "EnableCleanup": true
+ }
}
}
}
diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
index dcca8c2adbe0..1caa81d80a03 100644
--- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
@@ -219,6 +219,9 @@ @keyframes umbraco-preview-badge--effect {{
[DefaultValue(StaticLoginLogoImage)]
public string LoginLogoImage { get; set; } = StaticLoginLogoImage;
-
+ ///
+ /// Get or sets the model representing the global content version cleanup policy
+ ///
+ public ContentVersionCleanupPolicySettings ContentVersionCleanupPolicy { get; set; } = new ContentVersionCleanupPolicySettings();
}
}
diff --git a/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs b/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs
new file mode 100644
index 000000000000..bd460058eb3b
--- /dev/null
+++ b/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel;
+
+namespace Umbraco.Cms.Core.Configuration.Models
+{
+ ///
+ /// Model representing the global content version cleanup policy
+ ///
+ public class ContentVersionCleanupPolicySettings
+ {
+ private const bool StaticEnableCleanup = false;
+ private const int StaticKeepAllVersionsNewerThanDays = 7;
+ private const int StaticKeepLatestVersionPerDayForDays = 90;
+
+ ///
+ /// Gets or sets a value indicating whether or not the cleanup job should be executed.
+ ///
+ [DefaultValue(StaticEnableCleanup)]
+ public bool EnableCleanup { get; set; } = StaticEnableCleanup;
+
+ ///
+ /// Gets or sets the number of days where all historical content versions are kept.
+ ///
+ [DefaultValue(StaticKeepAllVersionsNewerThanDays)]
+ public int KeepAllVersionsNewerThanDays { get; set; } = StaticKeepAllVersionsNewerThanDays;
+
+ ///
+ /// Gets or sets the number of days where the latest historical content version for that day are kept.
+ ///
+ [DefaultValue(StaticKeepLatestVersionPerDayForDays)]
+ public int KeepLatestVersionPerDayForDays { get; set; } = StaticKeepLatestVersionPerDayForDays;
+
+ }
+}
diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs
index 79f3fba1fd0f..6198f0c6641c 100644
--- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs
@@ -29,6 +29,9 @@ protected ContentTypeSave()
[DataMember(Name = "allowedContentTypes")]
public IEnumerable AllowedContentTypes { get; set; }
+ [DataMember(Name = "historyCleanup")]
+ public HistoryCleanupViewModel HistoryCleanup { get; set; }
+
///
/// Custom validation
///
diff --git a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs
index 7d45c466002a..3bb52c39b724 100644
--- a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs
+++ b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models.ContentEditing
@@ -6,11 +6,9 @@ namespace Umbraco.Cms.Core.Models.ContentEditing
[DataContract(Name = "contentType", Namespace = "")]
public class DocumentTypeDisplay : ContentTypeCompositionDisplay
{
- public DocumentTypeDisplay()
- {
+ public DocumentTypeDisplay() =>
//initialize collections so at least their never null
AllowedTemplates = new List();
- }
//name, alias, icon, thumb, desc, inherited from the content type
@@ -29,5 +27,8 @@ public DocumentTypeDisplay()
[DataMember(Name = "apps")]
public IEnumerable ContentApps { get; set; }
+
+ [DataMember(Name = "historyCleanup")]
+ public HistoryCleanupViewModel HistoryCleanup { get; set; }
}
}
diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs
new file mode 100644
index 000000000000..b7bfb328087f
--- /dev/null
+++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs
@@ -0,0 +1,17 @@
+using System.Runtime.Serialization;
+
+namespace Umbraco.Cms.Core.Models.ContentEditing
+{
+ [DataContract(Name = "historyCleanup", Namespace = "")]
+ public class HistoryCleanup
+ {
+ [DataMember(Name = "preventCleanup")]
+ public bool PreventCleanup { get; set; }
+
+ [DataMember(Name = "keepAllVersionsNewerThanDays")]
+ public int? KeepAllVersionsNewerThanDays { get; set; }
+
+ [DataMember(Name = "keepLatestVersionPerDayForDays")]
+ public int? KeepLatestVersionPerDayForDays { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs
new file mode 100644
index 000000000000..303ff4eda375
--- /dev/null
+++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs
@@ -0,0 +1,18 @@
+using System.Runtime.Serialization;
+
+namespace Umbraco.Cms.Core.Models.ContentEditing
+{
+ [DataContract(Name = "historyCleanup", Namespace = "")]
+ public class HistoryCleanupViewModel : HistoryCleanup
+ {
+
+ [DataMember(Name = "globalEnableCleanup")]
+ public bool GlobalEnableCleanup { get; set; }
+
+ [DataMember(Name = "globalKeepAllVersionsNewerThanDays")]
+ public int? GlobalKeepAllVersionsNewerThanDays { get; set; }
+
+ [DataMember(Name = "globalKeepLatestVersionPerDayForDays")]
+ public int? GlobalKeepLatestVersionPerDayForDays { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs
index 9a0e1a6854ce..6ff94f57f363 100644
--- a/src/Umbraco.Core/Models/ContentType.cs
+++ b/src/Umbraco.Core/Models/ContentType.cs
@@ -1,37 +1,45 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models
{
///
- /// Represents the content type that a object is based on
+ /// Represents the content type that a object is based on
///
[Serializable]
[DataContract(IsReference = true)]
- public class ContentType : ContentTypeCompositionBase, IContentType
+ public class ContentType : ContentTypeCompositionBase, IContentTypeWithHistoryCleanup
{
public const bool SupportsPublishingConst = true;
- private int _defaultTemplate;
+ // Custom comparer for enumerable
+ private static readonly DelegateEqualityComparer> TemplateComparer = new (
+ (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
+ templates => templates.GetHashCode());
+
private IEnumerable _allowedTemplates;
+ private int _defaultTemplate;
+
///
- /// Constuctor for creating a ContentType with the parent's id.
+ /// Constuctor for creating a ContentType with the parent's id.
///
/// Only use this for creating ContentTypes at the root (with ParentId -1).
///
public ContentType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId)
{
_allowedTemplates = new List();
+ HistoryCleanup = new HistoryCleanup();
}
///
- /// Constuctor for creating a ContentType with the parent as an inherited type.
+ /// Constuctor for creating a ContentType with the parent as an inherited type.
///
/// Use this to ensure inheritance from parent.
///
@@ -40,30 +48,24 @@ public ContentType(IShortStringHelper shortStringHelper, IContentType parent, st
: base(shortStringHelper, parent, alias)
{
_allowedTemplates = new List();
+ HistoryCleanup = new HistoryCleanup();
}
- ///
- public override ISimpleContentType ToSimple() => new SimpleContentType(this);
-
///
public override bool SupportsPublishing => SupportsPublishingConst;
- //Custom comparer for enumerable
- private static readonly DelegateEqualityComparer> TemplateComparer = new DelegateEqualityComparer>(
- (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
- templates => templates.GetHashCode());
+ ///
+ public override ISimpleContentType ToSimple() => new SimpleContentType(this);
///
- /// Gets or sets the alias of the default Template.
- /// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
- /// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
+ /// Gets or sets the alias of the default Template.
+ /// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
+ /// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
/// we should not store direct entity
///
[IgnoreDataMember]
- public ITemplate DefaultTemplate
- {
- get { return AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); }
- }
+ public ITemplate DefaultTemplate =>
+ AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId);
[DataMember]
@@ -74,9 +76,9 @@ public int DefaultTemplateId
}
///
- /// Gets or Sets a list of Templates which are allowed for the ContentType
- /// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
- /// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
+ /// Gets or Sets a list of Templates which are allowed for the ContentType
+ /// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
+ /// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
/// we should not store direct entity
///
[DataMember]
@@ -88,38 +90,38 @@ public IEnumerable AllowedTemplates
SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, nameof(AllowedTemplates), TemplateComparer);
if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false)
+ {
DefaultTemplateId = 0;
+ }
}
}
+ public HistoryCleanup HistoryCleanup { get; set; }
+
///
- /// Determines if AllowedTemplates contains templateId
+ /// Determines if AllowedTemplates contains templateId
///
/// The template id to check
/// True if AllowedTemplates contains the templateId else False
- public bool IsAllowedTemplate(int templateId)
- {
- return AllowedTemplates == null
+ public bool IsAllowedTemplate(int templateId) =>
+ AllowedTemplates == null
? false
: AllowedTemplates.Any(t => t.Id == templateId);
- }
///
- /// Determines if AllowedTemplates contains templateId
+ /// Determines if AllowedTemplates contains templateId
///
/// The template alias to check
/// True if AllowedTemplates contains the templateAlias else False
- public bool IsAllowedTemplate(string templateAlias)
- {
- return AllowedTemplates == null
+ public bool IsAllowedTemplate(string templateAlias) =>
+ AllowedTemplates == null
? false
: AllowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase));
- }
///
- /// Sets the default template for the ContentType
+ /// Sets the default template for the ContentType
///
- /// Default
+ /// Default
public void SetDefaultTemplate(ITemplate template)
{
if (template == null)
@@ -138,17 +140,19 @@ public void SetDefaultTemplate(ITemplate template)
}
///
- /// Removes a template from the list of allowed templates
+ /// Removes a template from the list of allowed templates
///
- /// to remove
+ /// to remove
/// True if template was removed, otherwise False
public bool RemoveTemplate(ITemplate template)
{
if (DefaultTemplateId == template.Id)
- DefaultTemplateId = default(int);
+ {
+ DefaultTemplateId = default;
+ }
var templates = AllowedTemplates.ToList();
- var remove = templates.FirstOrDefault(x => x.Id == template.Id);
+ ITemplate remove = templates.FirstOrDefault(x => x.Id == template.Id);
var result = templates.Remove(remove);
AllowedTemplates = templates;
@@ -156,6 +160,7 @@ public bool RemoveTemplate(ITemplate template)
}
///
- IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) => (IContentType)DeepCloneWithResetIdentities(newAlias);
+ IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) =>
+ (IContentType)DeepCloneWithResetIdentities(newAlias);
}
}
diff --git a/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs b/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs
new file mode 100644
index 000000000000..5fa0e9895822
--- /dev/null
+++ b/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Umbraco.Cms.Core.Models
+{
+ public class ContentVersionCleanupPolicySettings
+ {
+ public int ContentTypeId { get; set; }
+
+ public bool PreventCleanup { get; set; }
+
+ public int? KeepAllVersionsNewerThanDays { get; set; }
+
+ public int? KeepLatestVersionPerDayForDays { get; set; }
+
+ public DateTime Updated { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/HistoricContentVersionMeta.cs b/src/Umbraco.Core/Models/HistoricContentVersionMeta.cs
new file mode 100644
index 000000000000..ce2c771ec6bf
--- /dev/null
+++ b/src/Umbraco.Core/Models/HistoricContentVersionMeta.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Umbraco.Cms.Core.Models
+{
+ public class HistoricContentVersionMeta
+ {
+ public int ContentId { get; }
+ public int ContentTypeId { get; }
+ public int VersionId { get; }
+ public DateTime VersionDate { get; }
+
+ public HistoricContentVersionMeta() { }
+
+ public HistoricContentVersionMeta(int contentId, int contentTypeId, int versionId, DateTime versionDate)
+ {
+ ContentId = contentId;
+ ContentTypeId = contentTypeId;
+ VersionId = versionId;
+ VersionDate = versionDate;
+ }
+
+ public override string ToString() => $"HistoricContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}";
+ }
+}
diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs
index f04a73d5e032..a01e6128878b 100644
--- a/src/Umbraco.Core/Models/IContentType.cs
+++ b/src/Umbraco.Core/Models/IContentType.cs
@@ -1,56 +1,67 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Models.ContentEditing;
namespace Umbraco.Cms.Core.Models
{
+ [Obsolete("This will be merged into IContentType in Umbraco 10")]
+ public interface IContentTypeWithHistoryCleanup : IContentType
+ {
+ ///
+ /// Gets or Sets the history cleanup configuration
+ ///
+ HistoryCleanup HistoryCleanup { get; set; }
+ }
+
///
- /// Defines a ContentType, which Content is based on
+ /// Defines a ContentType, which Content is based on
///
public interface IContentType : IContentTypeComposition
{
///
- /// Internal property to store the Id of the default template
+ /// Internal property to store the Id of the default template
///
int DefaultTemplateId { get; set; }
///
- /// Gets the default Template of the ContentType
+ /// Gets the default Template of the ContentType
///
ITemplate DefaultTemplate { get; }
///
- /// Gets or Sets a list of Templates which are allowed for the ContentType
+ /// Gets or Sets a list of Templates which are allowed for the ContentType
///
IEnumerable AllowedTemplates { get; set; }
///
- /// Determines if AllowedTemplates contains templateId
+ /// Determines if AllowedTemplates contains templateId
///
/// The template id to check
/// True if AllowedTemplates contains the templateId else False
bool IsAllowedTemplate(int templateId);
///
- /// Determines if AllowedTemplates contains templateId
+ /// Determines if AllowedTemplates contains templateId
///
/// The template alias to check
/// True if AllowedTemplates contains the templateAlias else False
bool IsAllowedTemplate(string templateAlias);
///
- /// Sets the default template for the ContentType
+ /// Sets the default template for the ContentType
///
- /// Default
+ /// Default
void SetDefaultTemplate(ITemplate template);
///
- /// Removes a template from the list of allowed templates
+ /// Removes a template from the list of allowed templates
///
- /// to remove
+ /// to remove
/// True if template was removed, otherwise False
bool RemoveTemplate(ITemplate template);
///
- /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
+ /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
///
///
///
diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
index d3269c2c0b54..debaa976c510 100644
--- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs
@@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Exceptions;
using Umbraco.Cms.Core.Hosting;
@@ -13,31 +13,50 @@
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
+using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models.Mapping
{
///
- /// Defines mappings for content/media/members type mappings
+ /// Defines mappings for content/media/members type mappings
///
public class ContentTypeMapDefinition : IMapDefinition
{
private readonly CommonMapper _commonMapper;
- private readonly PropertyEditorCollection _propertyEditors;
+ private readonly IContentTypeService _contentTypeService;
private readonly IDataTypeService _dataTypeService;
private readonly IFileService _fileService;
- private readonly IContentTypeService _contentTypeService;
+ private readonly GlobalSettings _globalSettings;
+ private readonly IHostingEnvironment _hostingEnvironment;
+ private readonly ILogger _logger;
+ private readonly ILoggerFactory _loggerFactory;
private readonly IMediaTypeService _mediaTypeService;
private readonly IMemberTypeService _memberTypeService;
- private readonly ILoggerFactory _loggerFactory;
- private readonly ILogger _logger;
+ private readonly PropertyEditorCollection _propertyEditors;
private readonly IShortStringHelper _shortStringHelper;
- private readonly GlobalSettings _globalSettings;
- private readonly IHostingEnvironment _hostingEnvironment;
+ private ContentSettings _contentSettings;
- public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService,
- IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService,
- ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, IHostingEnvironment hostingEnvironment)
+
+ [Obsolete("Use ctor with all params injected")]
+ public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors,
+ IDataTypeService dataTypeService, IFileService fileService,
+ IContentTypeService contentTypeService, IMediaTypeService mediaTypeService,
+ IMemberTypeService memberTypeService,
+ ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings,
+ IHostingEnvironment hostingEnvironment)
+ : this(commonMapper, propertyEditors, dataTypeService, fileService, contentTypeService, mediaTypeService,
+ memberTypeService, loggerFactory, shortStringHelper, globalSettings, hostingEnvironment,
+ StaticServiceProvider.Instance.GetRequiredService>())
+ {
+ }
+
+ public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors,
+ IDataTypeService dataTypeService, IFileService fileService,
+ IContentTypeService contentTypeService, IMediaTypeService mediaTypeService,
+ IMemberTypeService memberTypeService,
+ ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings,
+ IHostingEnvironment hostingEnvironment, IOptionsMonitor contentSettings)
{
_commonMapper = commonMapper;
_propertyEditors = propertyEditors;
@@ -51,13 +70,19 @@ public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollect
_shortStringHelper = shortStringHelper;
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
+
+ _contentSettings = contentSettings.CurrentValue;
+ contentSettings.OnChange(x => _contentSettings = x);
}
public void DefineMaps(IUmbracoMapper mapper)
{
- mapper.Define((source, context) => new ContentType(_shortStringHelper, source.ParentId), Map);
- mapper.Define((source, context) => new MediaType(_shortStringHelper, source.ParentId), Map);
- mapper.Define((source, context) => new MemberType(_shortStringHelper, source.ParentId), Map);
+ mapper.Define(
+ (source, context) => new ContentType(_shortStringHelper, source.ParentId), Map);
+ mapper.Define(
+ (source, context) => new MediaType(_shortStringHelper, source.ParentId), Map);
+ mapper.Define(
+ (source, context) => new MemberType(_shortStringHelper, source.ParentId), Map);
mapper.Define((source, context) => new DocumentTypeDisplay(), Map);
mapper.Define((source, context) => new MediaTypeDisplay(), Map);
@@ -66,14 +91,20 @@ public void DefineMaps(IUmbracoMapper mapper)
mapper.Define(
(source, context) =>
{
- var dataType = _dataTypeService.GetDataType(source.DataTypeId);
- if (dataType == null) throw new NullReferenceException("No data type found with id " + source.DataTypeId);
+ IDataType dataType = _dataTypeService.GetDataType(source.DataTypeId);
+ if (dataType == null)
+ {
+ throw new NullReferenceException("No data type found with id " + source.DataTypeId);
+ }
+
return new PropertyType(_shortStringHelper, dataType, source.Alias);
}, Map);
// TODO: isPublishing in ctor?
- mapper.Define, PropertyGroup>((source, context) => new PropertyGroup(false), Map);
- mapper.Define, PropertyGroup>((source, context) => new PropertyGroup(false), Map);
+ mapper.Define, PropertyGroup>(
+ (source, context) => new PropertyGroup(false), Map);
+ mapper.Define, PropertyGroup>(
+ (source, context) => new PropertyGroup(false), Map);
mapper.Define((source, context) => new ContentTypeBasic(), Map);
mapper.Define((source, context) => new ContentTypeBasic(), Map);
@@ -84,11 +115,14 @@ public void DefineMaps(IUmbracoMapper mapper)
mapper.Define((source, context) => new MediaTypeDisplay(), Map);
mapper.Define((source, context) => new MemberTypeDisplay(), Map);
- mapper.Define, PropertyGroupDisplay>((source, context) => new PropertyGroupDisplay(), Map);
- mapper.Define, PropertyGroupDisplay>((source, context) => new PropertyGroupDisplay(), Map);
+ mapper.Define, PropertyGroupDisplay>(
+ (source, context) => new PropertyGroupDisplay(), Map);
+ mapper.Define, PropertyGroupDisplay>(
+ (source, context) => new PropertyGroupDisplay(), Map);
mapper.Define((source, context) => new PropertyTypeDisplay(), Map);
- mapper.Define((source, context) => new MemberPropertyTypeDisplay(), Map);
+ mapper.Define(
+ (source, context) => new MemberPropertyTypeDisplay(), Map);
}
// no MapAll - take care
@@ -97,13 +131,20 @@ private void Map(DocumentTypeSave source, IContentType target, MapperContext con
MapSaveToTypeBase(source, target, context);
MapComposition(source, target, alias => _contentTypeService.Get(alias));
+ if (target is IContentTypeWithHistoryCleanup targetWithHistoryCleanup)
+ {
+ targetWithHistoryCleanup.HistoryCleanup = source.HistoryCleanup;
+ }
+
target.AllowedTemplates = source.AllowedTemplates
.Where(x => x != null)
.Select(_fileService.GetTemplate)
.Where(x => x != null)
.ToArray();
- target.SetDefaultTemplate(source.DefaultTemplate == null ? null : _fileService.GetTemplate(source.DefaultTemplate));
+ target.SetDefaultTemplate(source.DefaultTemplate == null
+ ? null
+ : _fileService.GetTemplate(source.DefaultTemplate));
}
// no MapAll - take care
@@ -119,11 +160,16 @@ private void Map(MemberTypeSave source, IMemberType target, MapperContext contex
MapSaveToTypeBase(source, target, context);
MapComposition(source, target, alias => _memberTypeService.Get(alias));
- foreach (var propertyType in source.Groups.SelectMany(x => x.Properties))
+ foreach (MemberPropertyTypeBasic propertyType in source.Groups.SelectMany(x => x.Properties))
{
- var localCopy = propertyType;
- var destProp = target.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias));
- if (destProp == null) continue;
+ MemberPropertyTypeBasic localCopy = propertyType;
+ IPropertyType destProp =
+ target.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias));
+ if (destProp == null)
+ {
+ continue;
+ }
+
target.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty);
target.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty);
target.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData);
@@ -135,6 +181,24 @@ private void Map(IContentType source, DocumentTypeDisplay target, MapperContext
{
MapTypeToDisplayBase(source, target);
+ if (source is IContentTypeWithHistoryCleanup sourceWithHistoryCleanup)
+ {
+ target.HistoryCleanup = new HistoryCleanupViewModel
+ {
+ PreventCleanup = sourceWithHistoryCleanup.HistoryCleanup.PreventCleanup,
+ KeepAllVersionsNewerThanDays =
+ sourceWithHistoryCleanup.HistoryCleanup.KeepAllVersionsNewerThanDays,
+ KeepLatestVersionPerDayForDays =
+ sourceWithHistoryCleanup.HistoryCleanup.KeepLatestVersionPerDayForDays,
+ GlobalKeepAllVersionsNewerThanDays =
+ _contentSettings.ContentVersionCleanupPolicy.KeepAllVersionsNewerThanDays,
+ GlobalKeepLatestVersionPerDayForDays =
+ _contentSettings.ContentVersionCleanupPolicy.KeepLatestVersionPerDayForDays,
+ GlobalEnableCleanup = _contentSettings.ContentVersionCleanupPolicy.EnableCleanup
+ };
+ }
+
+
target.AllowCultureVariant = source.VariesByCulture();
target.AllowSegmentVariant = source.VariesBySegment();
target.ContentApps = _commonMapper.GetContentApps(source);
@@ -143,16 +207,23 @@ private void Map(IContentType source, DocumentTypeDisplay target, MapperContext
target.AllowedTemplates = context.MapEnumerable(source.AllowedTemplates);
if (source.DefaultTemplate != null)
+ {
target.DefaultTemplate = context.Map(source.DefaultTemplate);
+ }
//default listview
target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content";
- if (string.IsNullOrEmpty(source.Alias)) return;
+ if (string.IsNullOrEmpty(source.Alias))
+ {
+ return;
+ }
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias;
if (_dataTypeService.GetDataType(name) != null)
+ {
target.ListViewEditorName = name;
+ }
}
// no MapAll - take care
@@ -164,11 +235,16 @@ private void Map(IMediaType source, MediaTypeDisplay target, MapperContext conte
target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media";
target.IsSystemMediaType = source.IsSystemMediaType();
- if (string.IsNullOrEmpty(source.Name)) return;
+ if (string.IsNullOrEmpty(source.Name))
+ {
+ return;
+ }
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name;
if (_dataTypeService.GetDataType(name) != null)
+ {
target.ListViewEditorName = name;
+ }
}
// no MapAll - take care
@@ -177,11 +253,16 @@ private void Map(IMemberType source, MemberTypeDisplay target, MapperContext con
MapTypeToDisplayBase(source, target);
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
- foreach (var propertyType in source.PropertyTypes)
+ foreach (IPropertyType propertyType in source.PropertyTypes)
{
- var localCopy = propertyType;
- var displayProp = target.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias));
- if (displayProp == null) continue;
+ IPropertyType localCopy = propertyType;
+ MemberPropertyTypeDisplay displayProp = target.Groups.SelectMany(dest => dest.Properties)
+ .SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias));
+ if (displayProp == null)
+ {
+ continue;
+ }
+
displayProp.MemberCanEditProperty = source.MemberCanEditProperty(localCopy.Alias);
displayProp.MemberCanViewProperty = source.MemberCanViewProperty(localCopy.Alias);
displayProp.IsSensitiveData = source.IsSensitiveProperty(localCopy.Alias);
@@ -216,28 +297,20 @@ private void Map(IContentTypeBase source, ContentTypeBasic target, string entity
}
// no MapAll - uses the IContentTypeBase map method, which has MapAll
- private void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context)
- {
+ private void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context) =>
Map(source, target, Constants.UdiEntityType.MemberType);
- }
// no MapAll - uses the IContentTypeBase map method, which has MapAll
- private void Map(IContentType source, ContentTypeBasic target, MapperContext context)
- {
+ private void Map(IContentType source, ContentTypeBasic target, MapperContext context) =>
Map(source, target, Constants.UdiEntityType.DocumentType);
- }
// no MapAll - uses the IContentTypeBase map method, which has MapAll
- private void Map(IMediaType source, ContentTypeBasic target, MapperContext context)
- {
+ private void Map(IMediaType source, ContentTypeBasic target, MapperContext context) =>
Map(source, target, Constants.UdiEntityType.MediaType);
- }
// no MapAll - uses the IContentTypeBase map method, which has MapAll
- private void Map(IMemberType source, ContentTypeBasic target, MapperContext context)
- {
+ private void Map(IMemberType source, ContentTypeBasic target, MapperContext context) =>
Map(source, target, Constants.UdiEntityType.MemberType);
- }
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate
// Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations
@@ -254,10 +327,14 @@ private static void Map(PropertyTypeBasic source, IPropertyType target, MapperCo
target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant);
if (source.Id > 0)
+ {
target.Id = source.Id;
+ }
if (source.GroupId > 0)
+ {
target.PropertyGroupId = new Lazy(() => source.GroupId, false);
+ }
target.Alias = source.Alias;
target.Description = source.Description;
@@ -268,18 +345,19 @@ private static void Map(PropertyTypeBasic source, IPropertyType target, MapperCo
// no MapAll - take care
private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperContext context)
{
- MapTypeToDisplayBase(source, target, context);
+ MapTypeToDisplayBase(source,
+ target, context);
//sync templates
- var destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias);
+ IEnumerable destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias);
//if the dest is set and it's the same as the source, then don't change
if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false)
{
- var templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray());
+ IEnumerable templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray());
target.AllowedTemplates = source.AllowedTemplates
.Select(x =>
{
- var template = templates.SingleOrDefault(t => t.Alias == x);
+ ITemplate template = templates.SingleOrDefault(t => t.Alias == x);
return template != null
? context.Map(template)
: null;
@@ -293,7 +371,7 @@ private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperCont
//if the dest is set and it's the same as the source, then don't change
if (target.DefaultTemplate == null || source.DefaultTemplate != target.DefaultTemplate.Alias)
{
- var template = _fileService.GetTemplate(source.DefaultTemplate);
+ ITemplate template = _fileService.GetTemplate(source.DefaultTemplate);
target.DefaultTemplate = template == null ? null : context.Map(template);
}
}
@@ -304,22 +382,24 @@ private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperCont
}
// no MapAll - take care
- private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context)
- {
- MapTypeToDisplayBase(source, target, context);
- }
+ private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context) =>
+ MapTypeToDisplayBase(source,
+ target, context);
// no MapAll - take care
- private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context)
- {
- MapTypeToDisplayBase(source, target, context);
- }
+ private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context) =>
+ MapTypeToDisplayBase(
+ source, target, context);
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes
- private static void Map(PropertyGroupBasic source, PropertyGroup target, MapperContext context)
+ private static void Map(PropertyGroupBasic source, PropertyGroup target,
+ MapperContext context)
{
if (source.Id > 0)
+ {
target.Id = source.Id;
+ }
+
target.Key = source.Key;
target.Type = source.Type;
target.Name = source.Name;
@@ -328,10 +408,14 @@ private static void Map(PropertyGroupBasic source, PropertyGr
}
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes
- private static void Map(PropertyGroupBasic source, PropertyGroup target, MapperContext context)
+ private static void Map(PropertyGroupBasic source, PropertyGroup target,
+ MapperContext context)
{
if (source.Id > 0)
+ {
target.Id = source.Id;
+ }
+
target.Key = source.Key;
target.Type = source.Type;
target.Name = source.Name;
@@ -340,11 +424,15 @@ private static void Map(PropertyGroupBasic source, Prop
}
// Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames
- private static void Map(PropertyGroupBasic source, PropertyGroupDisplay target, MapperContext context)
+ private static void Map(PropertyGroupBasic source,
+ PropertyGroupDisplay target, MapperContext context)
{
target.Inherited = source.Inherited;
if (source.Id > 0)
+ {
target.Id = source.Id;
+ }
+
target.Key = source.Key;
target.Type = source.Type;
target.Name = source.Name;
@@ -354,17 +442,22 @@ private static void Map(PropertyGroupBasic source, PropertyGr
}
// Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames
- private static void Map(PropertyGroupBasic source, PropertyGroupDisplay target, MapperContext context)
+ private static void Map(PropertyGroupBasic source,
+ PropertyGroupDisplay target, MapperContext context)
{
target.Inherited = source.Inherited;
if (source.Id > 0)
+ {
target.Id = source.Id;
+ }
+
target.Key = source.Key;
target.Type = source.Type;
target.Name = source.Name;
target.Alias = source.Alias;
target.SortOrder = source.SortOrder;
- target.Properties = context.MapEnumerable(source.Properties);
+ target.Properties =
+ context.MapEnumerable(source.Properties);
}
// Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName
@@ -409,7 +502,8 @@ private static void Map(MemberPropertyTypeBasic source, MemberPropertyTypeDispla
// Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
// Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType)
- private static void MapSaveToTypeBase(TSource source, IContentTypeComposition target, MapperContext context)
+ private static void MapSaveToTypeBase(TSource source,
+ IContentTypeComposition target, MapperContext context)
where TSource : ContentTypeSave
where TSourcePropertyType : PropertyTypeBasic
{
@@ -418,7 +512,9 @@ private static void MapSaveToTypeBase(TSource sour
var id = Convert.ToInt32(source.Id);
if (id > 0)
+ {
target.Id = id;
+ }
target.Alias = source.Alias;
target.Description = source.Description;
@@ -457,18 +553,19 @@ private static void MapSaveToTypeBase(TSource sour
// - managing the content type's PropertyTypes collection (for generic properties)
// handle actual groups (non-generic-properties)
- var destOrigGroups = target.PropertyGroups.ToArray(); // local groups
- var destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not
+ PropertyGroup[] destOrigGroups = target.PropertyGroups.ToArray(); // local groups
+ IPropertyType[] destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not
var destGroups = new List();
- var sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray();
+ PropertyGroupBasic[] sourceGroups =
+ source.Groups.Where(x => x.IsGenericProperties == false).ToArray();
var sourceGroupParentAliases = sourceGroups.Select(x => x.GetParentAlias()).Distinct().ToArray();
- foreach (var sourceGroup in sourceGroups)
+ foreach (PropertyGroupBasic sourceGroup in sourceGroups)
{
// get the dest group
- var destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context);
+ PropertyGroup destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context);
// handle local properties
- var destProperties = sourceGroup.Properties
+ IPropertyType[] destProperties = sourceGroup.Properties
.Where(x => x.Inherited == false)
.Select(x => MapSaveProperty(x, destOrigProperties, context))
.ToArray();
@@ -476,7 +573,9 @@ private static void MapSaveToTypeBase(TSource sour
// if the group has no local properties and is not used as parent, skip it, ie sort-of garbage-collect
// local groups which would not have local properties anymore
if (destProperties.Length == 0 && !sourceGroupParentAliases.Contains(sourceGroup.Alias))
+ {
continue;
+ }
// ensure no duplicate alias, then assign the group properties collection
EnsureUniqueAliases(destProperties);
@@ -492,11 +591,12 @@ private static void MapSaveToTypeBase(TSource sour
// the old groups - they are just gone and will be cleared by the repository
// handle non-grouped (ie generic) properties
- var genericPropertiesGroup = source.Groups.FirstOrDefault(x => x.IsGenericProperties);
+ PropertyGroupBasic genericPropertiesGroup =
+ source.Groups.FirstOrDefault(x => x.IsGenericProperties);
if (genericPropertiesGroup != null)
{
// handle local properties
- var destProperties = genericPropertiesGroup.Properties
+ IPropertyType[] destProperties = genericPropertiesGroup.Properties
.Where(x => x.Inherited == false)
.Select(x => MapSaveProperty(x, destOrigProperties, context))
.ToArray();
@@ -547,7 +647,8 @@ private void MapTypeToDisplayBase(IContentTypeComp
{
MapTypeToDisplayBase(source, target);
- var groupsMapper = new PropertyTypeGroupMapper(_propertyEditors, _dataTypeService, _shortStringHelper, _loggerFactory.CreateLogger>());
+ var groupsMapper = new PropertyTypeGroupMapper(_propertyEditors, _dataTypeService,
+ _shortStringHelper, _loggerFactory.CreateLogger>());
target.Groups = groupsMapper.Map(source);
}
@@ -580,7 +681,8 @@ private void MapTypeToDisplayBase(ContentTypeSave source, ContentTypeComposition
}
// no MapAll - relies on the non-generic method
- private void MapTypeToDisplayBase(TSource source, TTarget target, MapperContext context)
+ private void MapTypeToDisplayBase(TSource source,
+ TTarget target, MapperContext context)
where TSource : ContentTypeSave
where TSourcePropertyType : PropertyTypeBasic
where TTarget : ContentTypeCompositionDisplay
@@ -588,35 +690,49 @@ private void MapTypeToDisplayBase, PropertyGroupDisplay>(source.Groups);
+ target.Groups =
+ context
+ .MapEnumerable, PropertyGroupDisplay>(
+ source.Groups);
}
private IEnumerable MapLockedCompositions(IContentTypeComposition source)
{
// get ancestor ids from path of parent if not root
if (source.ParentId == Constants.System.Root)
+ {
return Enumerable.Empty();
+ }
- var parent = _contentTypeService.Get(source.ParentId);
+ IContentType parent = _contentTypeService.Get(source.ParentId);
if (parent == null)
+ {
return Enumerable.Empty();
+ }
var aliases = new List();
- var ancestorIds = parent.Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture));
+ IEnumerable ancestorIds = parent.Path.Split(Constants.CharArrays.Comma)
+ .Select(s => int.Parse(s, CultureInfo.InvariantCulture));
// loop through all content types and return ordered aliases of ancestors
- var allContentTypes = _contentTypeService.GetAll().ToArray();
+ IContentType[] allContentTypes = _contentTypeService.GetAll().ToArray();
foreach (var ancestorId in ancestorIds)
{
- var ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId);
+ IContentType ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId);
if (ancestor != null)
+ {
aliases.Add(ancestor.Alias);
+ }
}
+
return aliases.OrderBy(x => x);
}
public static Udi MapContentTypeUdi(IContentTypeComposition source)
{
- if (source == null) return null;
+ if (source == null)
+ {
+ return null;
+ }
string udiType;
switch (source)
@@ -637,7 +753,8 @@ public static Udi MapContentTypeUdi(IContentTypeComposition source)
return Udi.Create(udiType, source.Key);
}
- private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups, MapperContext context)
+ private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup,
+ IEnumerable destOrigGroups, MapperContext context)
where TPropertyType : PropertyTypeBasic
{
PropertyGroup destGroup;
@@ -663,7 +780,8 @@ private static PropertyGroup MapSaveGroup(PropertyGroupBasic destOrigProperties, MapperContext context)
+ private static IPropertyType MapSaveProperty(PropertyTypeBasic sourceProperty,
+ IEnumerable destOrigProperties, MapperContext context)
{
IPropertyType destProperty;
if (sourceProperty.Id > 0)
@@ -690,43 +808,52 @@ private static IPropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, I
private static void EnsureUniqueAliases(IEnumerable properties)
{
- var propertiesA = properties.ToArray();
+ IPropertyType[] propertiesA = properties.ToArray();
var distinctProperties = propertiesA
.Select(x => x.Alias?.ToUpperInvariant())
.Distinct()
.Count();
if (distinctProperties != propertiesA.Length)
+ {
throw new InvalidOperationException("Cannot map properties due to alias conflict.");
+ }
}
private static void EnsureUniqueAliases(IEnumerable groups)
{
- var groupsA = groups.ToArray();
+ PropertyGroup[] groupsA = groups.ToArray();
var distinctProperties = groupsA
.Select(x => x.Alias)
.Distinct()
.Count();
if (distinctProperties != groupsA.Length)
+ {
throw new InvalidOperationException("Cannot map groups due to alias conflict.");
+ }
}
- private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, Func getContentType)
+ private static void MapComposition(ContentTypeSave source, IContentTypeComposition target,
+ Func getContentType)
{
var current = target.CompositionAliases().ToArray();
- var proposed = source.CompositeContentTypes;
+ IEnumerable proposed = source.CompositeContentTypes;
- var remove = current.Where(x => !proposed.Contains(x));
- var add = proposed.Where(x => !current.Contains(x));
+ IEnumerable remove = current.Where(x => !proposed.Contains(x));
+ IEnumerable add = proposed.Where(x => !current.Contains(x));
foreach (var alias in remove)
+ {
target.RemoveContentType(alias);
+ }
foreach (var alias in add)
{
// TODO: Remove N+1 lookup
- var contentType = getContentType(alias);
+ IContentTypeComposition contentType = getContentType(alias);
if (contentType != null)
+ {
target.AddContentType(contentType);
+ }
}
}
}
diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
index f0f891d48e0a..34c6284993ad 100644
--- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
+++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
@@ -26,6 +26,8 @@ public static class Tables
public const string Content = TableNamePrefix + "Content";
public const string ContentVersion = TableNamePrefix + "ContentVersion";
public const string ContentVersionCultureVariation = TableNamePrefix + "ContentVersionCultureVariation";
+ public const string ContentVersionCleanupPolicy = TableNamePrefix + "ContentVersionCleanupPolicy";
+
public const string Document = TableNamePrefix + "Document";
public const string DocumentCultureVariation = TableNamePrefix + "DocumentCultureVariation";
public const string DocumentVersion = TableNamePrefix + "DocumentVersion";
diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs
new file mode 100644
index 000000000000..8175ba0d99a3
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.Persistence.Repositories
+{
+ public interface IDocumentVersionRepository : IRepository
+ {
+ ///
+ /// Gets a list of all historic content versions.
+ ///
+ public IReadOnlyCollection GetDocumentVersionsEligibleForCleanup();
+
+ ///
+ /// Gets cleanup policy override settings per content type.
+ ///
+ public IReadOnlyCollection GetCleanupPolicies();
+
+ ///
+ /// Deletes multiple content versions by ID.
+ ///
+ void DeleteVersions(IEnumerable versionIds);
+ }
+}
diff --git a/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs b/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs
new file mode 100644
index 000000000000..d2f70206b087
--- /dev/null
+++ b/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.Services
+{
+ ///
+ /// Used to filter historic content versions for cleanup.
+ ///
+ public interface IContentVersionCleanupPolicy
+ {
+ ///
+ /// Filters a set of candidates historic content versions for cleanup according to policy settings.
+ ///
+ IEnumerable Apply(DateTime asAtDate, IEnumerable items);
+ }
+}
diff --git a/src/Umbraco.Core/Services/IContentVersionService.cs b/src/Umbraco.Core/Services/IContentVersionService.cs
new file mode 100644
index 000000000000..882618780fe6
--- /dev/null
+++ b/src/Umbraco.Core/Services/IContentVersionService.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.Services
+{
+ public interface IContentVersionService
+ {
+ ///
+ /// Removes historic content versions according to a policy.
+ ///
+ IReadOnlyCollection PerformContentVersionCleanup(DateTime asAtDate);
+ }
+}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 8ed2205f5950..56cb1e6d01eb 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -15,20 +15,20 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
+
+
+
all
@@ -56,6 +56,10 @@
-
+
+
+
+
+
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
index 360e7859068c..d3ebb28f9c53 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs
@@ -25,6 +25,7 @@ internal static IUmbracoBuilder AddRepositories(this IUmbracoBuilder builder)
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
+ builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
index 861a05b45907..661ed9329292 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs
@@ -6,7 +6,6 @@
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
-using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Hosting;
@@ -16,6 +15,7 @@
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Implement;
using Umbraco.Cms.Infrastructure.Packaging;
+using Umbraco.Cms.Infrastructure.Services.Implement;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.DependencyInjection
@@ -43,6 +43,8 @@ internal static IUmbracoBuilder AddServices(this IUmbracoBuilder builder)
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
diff --git a/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs
new file mode 100644
index 000000000000..5f3aba5f3f3d
--- /dev/null
+++ b/src/Umbraco.Infrastructure/HostedServices/ContentVersionCleanup.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.Runtime;
+using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Sync;
+
+namespace Umbraco.Cms.Infrastructure.HostedServices
+{
+ ///
+ /// Recurring hosted service that executes the content history cleanup.
+ ///
+ public class ContentVersionCleanup : RecurringHostedServiceBase
+ {
+ private readonly IRuntimeState _runtimeState;
+ private readonly ILogger _logger;
+ private readonly IOptionsMonitor _settingsMonitor;
+ private readonly IContentVersionService _service;
+ private readonly IMainDom _mainDom;
+ private readonly IServerRoleAccessor _serverRoleAccessor;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentVersionCleanup(
+ IRuntimeState runtimeState,
+ ILogger logger,
+ IOptionsMonitor settingsMonitor,
+ IContentVersionService service,
+ IMainDom mainDom,
+ IServerRoleAccessor serverRoleAccessor)
+ : base(TimeSpan.FromHours(1), TimeSpan.FromMinutes(3))
+ {
+ _runtimeState = runtimeState;
+ _logger = logger;
+ _settingsMonitor = settingsMonitor;
+ _service = service;
+ _mainDom = mainDom;
+ _serverRoleAccessor = serverRoleAccessor;
+ }
+
+ ///
+ public override Task PerformExecuteAsync(object state)
+ {
+ // Globally disabled by feature flag
+ if (!_settingsMonitor.CurrentValue.ContentVersionCleanupPolicy.EnableCleanup)
+ {
+ _logger.LogInformation("ContentVersionCleanup task will not run as it has been globally disabled via configuration");
+ return Task.CompletedTask;
+ }
+
+ if (_runtimeState.Level != RuntimeLevel.Run)
+ {
+ return Task.FromResult(true); // repeat...
+ }
+
+ // Don't run on replicas nor unknown role servers
+ switch (_serverRoleAccessor.CurrentServerRole)
+ {
+ case ServerRole.Subscriber:
+ _logger.LogDebug("Does not run on subscriber servers");
+ return Task.CompletedTask;
+ case ServerRole.Unknown:
+ _logger.LogDebug("Does not run on servers with unknown role");
+ return Task.CompletedTask;
+ case ServerRole.Single:
+ case ServerRole.SchedulingPublisher:
+ default:
+ break;
+ }
+
+ // Ensure we do not run if not main domain, but do NOT lock it
+ if (!_mainDom.IsMainDom)
+ {
+ _logger.LogDebug("Does not run if not MainDom");
+ return Task.FromResult(false); // do NOT repeat, going down
+ }
+
+ var count = _service.PerformContentVersionCleanup(DateTime.Now).Count;
+
+ if (count > 0)
+ {
+ _logger.LogInformation("Deleted {count} ContentVersion(s)", count);
+ }
+ else
+ {
+ _logger.LogDebug("Task complete, no items were Deleted");
+ }
+
+ return Task.FromResult(true);
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
index 4052a8ae56f1..c20c157604b4 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs
@@ -12,21 +12,82 @@
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
using Umbraco.Extensions;
+using ColumnInfo = Umbraco.Cms.Infrastructure.Persistence.SqlSyntax.ColumnInfo;
namespace Umbraco.Cms.Infrastructure.Migrations.Install
{
///
- /// Creates the initial database schema during install.
+ /// Creates the initial database schema during install.
///
public class DatabaseSchemaCreator
{
+ // all tables, in order
+ internal static readonly List OrderedTables = new()
+ {
+ typeof(UserDto),
+ typeof(NodeDto),
+ typeof(ContentTypeDto),
+ typeof(TemplateDto),
+ typeof(ContentDto),
+ typeof(ContentVersionDto),
+ typeof(MediaVersionDto),
+ typeof(DocumentDto),
+ typeof(ContentTypeTemplateDto),
+ typeof(DataTypeDto),
+ typeof(DictionaryDto),
+ typeof(LanguageDto),
+ typeof(LanguageTextDto),
+ typeof(DomainDto),
+ typeof(LogDto),
+ typeof(MacroDto),
+ typeof(MacroPropertyDto),
+ typeof(MemberPropertyTypeDto),
+ typeof(MemberDto),
+ typeof(Member2MemberGroupDto),
+ typeof(PropertyTypeGroupDto),
+ typeof(PropertyTypeDto),
+ typeof(PropertyDataDto),
+ typeof(RelationTypeDto),
+ typeof(RelationDto),
+ typeof(TagDto),
+ typeof(TagRelationshipDto),
+ typeof(ContentType2ContentTypeDto),
+ typeof(ContentTypeAllowedContentTypeDto),
+ typeof(User2NodeNotifyDto),
+ typeof(ServerRegistrationDto),
+ typeof(AccessDto),
+ typeof(AccessRuleDto),
+ typeof(CacheInstructionDto),
+ typeof(ExternalLoginDto),
+ typeof(ExternalLoginTokenDto),
+ typeof(RedirectUrlDto),
+ typeof(LockDto),
+ typeof(UserGroupDto),
+ typeof(User2UserGroupDto),
+ typeof(UserGroup2NodePermissionDto),
+ typeof(UserGroup2AppDto),
+ typeof(UserStartNodeDto),
+ typeof(ContentNuDto),
+ typeof(DocumentVersionDto),
+ typeof(KeyValueDto),
+ typeof(UserLoginDto),
+ typeof(ConsentDto),
+ typeof(AuditEntryDto),
+ typeof(ContentVersionCultureVariationDto),
+ typeof(DocumentCultureVariationDto),
+ typeof(ContentScheduleDto),
+ typeof(LogViewerQueryDto),
+ typeof(ContentVersionCleanupPolicyDto)
+ };
+
private readonly IUmbracoDatabase _database;
+ private readonly IEventAggregator _eventAggregator;
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IUmbracoVersion _umbracoVersion;
- private readonly IEventAggregator _eventAggregator;
- public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger, ILoggerFactory loggerFactory, IUmbracoVersion umbracoVersion, IEventAggregator eventAggregator)
+ public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger,
+ ILoggerFactory loggerFactory, IUmbracoVersion umbracoVersion, IEventAggregator eventAggregator)
{
_database = database ?? throw new ArgumentNullException(nameof(database));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -42,74 +103,16 @@ public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger _database.SqlContext.SqlSyntax;
- // all tables, in order
- internal static readonly List OrderedTables = new List
- {
- typeof (UserDto),
- typeof (NodeDto),
- typeof (ContentTypeDto),
- typeof (TemplateDto),
- typeof (ContentDto),
- typeof (ContentVersionDto),
- typeof (MediaVersionDto),
- typeof (DocumentDto),
- typeof (ContentTypeTemplateDto),
- typeof (DataTypeDto),
- typeof (DictionaryDto),
- typeof (LanguageDto),
- typeof (LanguageTextDto),
- typeof (DomainDto),
- typeof (LogDto),
- typeof (MacroDto),
- typeof (MacroPropertyDto),
- typeof (MemberPropertyTypeDto),
- typeof (MemberDto),
- typeof (Member2MemberGroupDto),
- typeof (PropertyTypeGroupDto),
- typeof (PropertyTypeDto),
- typeof (PropertyDataDto),
- typeof (RelationTypeDto),
- typeof (RelationDto),
- typeof (TagDto),
- typeof (TagRelationshipDto),
- typeof (ContentType2ContentTypeDto),
- typeof (ContentTypeAllowedContentTypeDto),
- typeof (User2NodeNotifyDto),
- typeof (ServerRegistrationDto),
- typeof (AccessDto),
- typeof (AccessRuleDto),
- typeof (CacheInstructionDto),
- typeof (ExternalLoginDto),
- typeof (ExternalLoginTokenDto),
- typeof (RedirectUrlDto),
- typeof (LockDto),
- typeof (UserGroupDto),
- typeof (User2UserGroupDto),
- typeof (UserGroup2NodePermissionDto),
- typeof (UserGroup2AppDto),
- typeof (UserStartNodeDto),
- typeof (ContentNuDto),
- typeof (DocumentVersionDto),
- typeof (KeyValueDto),
- typeof (UserLoginDto),
- typeof (ConsentDto),
- typeof (AuditEntryDto),
- typeof (ContentVersionCultureVariationDto),
- typeof (DocumentCultureVariationDto),
- typeof (ContentScheduleDto),
- typeof (LogViewerQueryDto)
- };
-
///
- /// Drops all Umbraco tables in the db.
+ /// Drops all Umbraco tables in the db.
///
internal void UninstallDatabaseSchema()
{
_logger.LogInformation("Start UninstallDatabaseSchema");
- foreach (var table in OrderedTables.AsEnumerable().Reverse())
+ foreach (Type table in OrderedTables.AsEnumerable().Reverse())
{
- var tableNameAttribute = table.FirstAttribute();
+ TableNameAttribute tableNameAttribute = table.FirstAttribute();
var tableName = tableNameAttribute == null ? table.Name : tableNameAttribute.Value;
_logger.LogInformation("Uninstall {TableName}", tableName);
@@ -117,7 +120,9 @@ internal void UninstallDatabaseSchema()
try
{
if (TableExists(tableName))
+ {
DropTable(tableName);
+ }
}
catch (Exception ex)
{
@@ -129,13 +134,15 @@ internal void UninstallDatabaseSchema()
}
///
- /// Initializes the database by creating the umbraco db schema.
+ /// Initializes the database by creating the umbraco db schema.
///
/// This needs to execute as part of a transaction.
public void InitializeDatabaseSchema()
{
if (!_database.InTransaction)
+ {
throw new InvalidOperationException("Database is not in a transaction.");
+ }
var eventMessages = new EventMessages();
var creatingNotification = new DatabaseSchemaCreatingNotification(eventMessages);
@@ -143,22 +150,23 @@ public void InitializeDatabaseSchema()
if (creatingNotification.Cancel == false)
{
- var dataCreation = new DatabaseDataCreator(_database, _loggerFactory.CreateLogger(), _umbracoVersion);
- foreach (var table in OrderedTables)
+ var dataCreation = new DatabaseDataCreator(_database,
+ _loggerFactory.CreateLogger(), _umbracoVersion);
+ foreach (Type table in OrderedTables)
+ {
CreateTable(false, table, dataCreation);
+ }
}
- DatabaseSchemaCreatedNotification createdNotification = new DatabaseSchemaCreatedNotification(eventMessages).WithStateFrom(creatingNotification);
+ DatabaseSchemaCreatedNotification createdNotification =
+ new DatabaseSchemaCreatedNotification(eventMessages).WithStateFrom(creatingNotification);
FireAfterCreation(createdNotification);
}
///
- /// Validates the schema of the current database.
+ /// Validates the schema of the current database.
///
- internal DatabaseSchemaResult ValidateSchema()
- {
- return ValidateSchema(OrderedTables);
- }
+ internal DatabaseSchemaResult ValidateSchema() => ValidateSchema(OrderedTables);
internal DatabaseSchemaResult ValidateSchema(IEnumerable orderedTables)
{
@@ -179,26 +187,30 @@ internal DatabaseSchemaResult ValidateSchema(IEnumerable orderedTables)
}
///
- /// This validates the Primary/Foreign keys in the database
+ /// This validates the Primary/Foreign keys in the database
///
///
///
- /// This does not validate any database constraints that are not PKs or FKs because Umbraco does not create a database with non PK/FK constraints.
- /// Any unique "constraints" in the database are done with unique indexes.
+ /// This does not validate any database constraints that are not PKs or FKs because Umbraco does not create a database
+ /// with non PK/FK constraints.
+ /// Any unique "constraints" in the database are done with unique indexes.
///
private void ValidateDbConstraints(DatabaseSchemaResult result)
{
//Check constraints in configured database against constraints in schema
var constraintsInDatabase = SqlSyntax.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList();
- var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")).Select(x => x.Item3).ToList();
- var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")).Select(x => x.Item3).ToList();
+ var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_"))
+ .Select(x => x.Item3).ToList();
+ var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_"))
+ .Select(x => x.Item3).ToList();
var unknownConstraintsInDatabase =
constraintsInDatabase.Where(
x =>
- x.Item3.InvariantStartsWith("FK_") == false && x.Item3.InvariantStartsWith("PK_") == false &&
- x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList();
- var foreignKeysInSchema = result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList();
+ x.Item3.InvariantStartsWith("FK_") == false && x.Item3.InvariantStartsWith("PK_") == false &&
+ x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList();
+ var foreignKeysInSchema =
+ result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList();
var primaryKeysInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => y.PrimaryKeyName))
.Where(x => x.IsNullOrWhiteSpace() == false).ToList();
@@ -219,14 +231,17 @@ private void ValidateDbConstraints(DatabaseSchemaResult result)
//Foreign keys:
- var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase);
+ IEnumerable validForeignKeyDifferences =
+ foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase);
foreach (var foreignKey in validForeignKeyDifferences)
{
result.ValidConstraints.Add(foreignKey);
}
- var invalidForeignKeyDifferences =
+
+ IEnumerable invalidForeignKeyDifferences =
foreignKeysInDatabase.Except(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase)
- .Union(foreignKeysInSchema.Except(foreignKeysInDatabase, StringComparer.InvariantCultureIgnoreCase));
+ .Union(foreignKeysInSchema.Except(foreignKeysInDatabase,
+ StringComparer.InvariantCultureIgnoreCase));
foreach (var foreignKey in invalidForeignKeyDifferences)
{
result.Errors.Add(new Tuple("Constraint", foreignKey));
@@ -236,37 +251,43 @@ private void ValidateDbConstraints(DatabaseSchemaResult result)
//Primary keys:
//Add valid and invalid primary key differences to the result object
- var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase);
+ IEnumerable validPrimaryKeyDifferences =
+ primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase);
foreach (var primaryKey in validPrimaryKeyDifferences)
{
result.ValidConstraints.Add(primaryKey);
}
- var invalidPrimaryKeyDifferences =
+
+ IEnumerable invalidPrimaryKeyDifferences =
primaryKeysInDatabase.Except(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase)
- .Union(primaryKeysInSchema.Except(primaryKeysInDatabase, StringComparer.InvariantCultureIgnoreCase));
+ .Union(primaryKeysInSchema.Except(primaryKeysInDatabase,
+ StringComparer.InvariantCultureIgnoreCase));
foreach (var primaryKey in invalidPrimaryKeyDifferences)
{
result.Errors.Add(new Tuple("Constraint", primaryKey));
}
-
}
private void ValidateDbColumns(DatabaseSchemaResult result)
{
//Check columns in configured database against columns in schema
- var columnsInDatabase = SqlSyntax.GetColumnsInSchema(_database);
- var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList();
- var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList();
+ IEnumerable columnsInDatabase = SqlSyntax.GetColumnsInSchema(_database);
+ var columnsPerTableInDatabase =
+ columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList();
+ var columnsPerTableInSchema = result.TableDefinitions
+ .SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList();
//Add valid and invalid column differences to the result object
- var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase);
+ IEnumerable validColumnDifferences =
+ columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase);
foreach (var column in validColumnDifferences)
{
result.ValidColumns.Add(column);
}
- var invalidColumnDifferences =
+ IEnumerable invalidColumnDifferences =
columnsPerTableInDatabase.Except(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase)
- .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase, StringComparer.InvariantCultureIgnoreCase));
+ .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase,
+ StringComparer.InvariantCultureIgnoreCase));
foreach (var column in invalidColumnDifferences)
{
result.Errors.Add(new Tuple("Column", column));
@@ -279,15 +300,16 @@ private void ValidateDbTables(DatabaseSchemaResult result)
var tablesInDatabase = SqlSyntax.GetTablesInSchema(_database).ToList();
var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList();
//Add valid and invalid table differences to the result object
- var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase);
+ IEnumerable validTableDifferences =
+ tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase);
foreach (var tableName in validTableDifferences)
{
result.ValidTables.Add(tableName);
}
- var invalidTableDifferences =
+ IEnumerable invalidTableDifferences =
tablesInDatabase.Except(tablesInSchema, StringComparer.InvariantCultureIgnoreCase)
- .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase));
+ .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase));
foreach (var tableName in invalidTableDifferences)
{
result.Errors.Add(new Tuple("Table", tableName));
@@ -302,15 +324,16 @@ private void ValidateDbIndexes(DatabaseSchemaResult result)
var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList();
//Add valid and invalid index differences to the result object
- var validColIndexDifferences = colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase);
+ IEnumerable validColIndexDifferences =
+ colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase);
foreach (var index in validColIndexDifferences)
{
result.ValidIndexes.Add(index);
}
- var invalidColIndexDifferences =
+ IEnumerable invalidColIndexDifferences =
colIndexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase)
- .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase));
+ .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase));
foreach (var index in invalidColIndexDifferences)
{
result.Errors.Add(new Tuple("Index", index));
@@ -320,14 +343,14 @@ private void ValidateDbIndexes(DatabaseSchemaResult result)
#region Notifications
///
- /// Publishes the notification.
+ /// Publishes the notification.
///
/// Cancelable notification marking the creation having begun.
internal virtual void FireBeforeCreation(DatabaseSchemaCreatingNotification notification) =>
_eventAggregator.Publish(notification);
///
- /// Publishes the notification.
+ /// Publishes the notification.
///
/// Notification marking the creation having completed.
internal virtual void FireAfterCreation(DatabaseSchemaCreatedNotification notification) =>
@@ -338,30 +361,27 @@ internal virtual void FireAfterCreation(DatabaseSchemaCreatedNotification notifi
#region Utilities
///
- /// Returns whether a table with the specified exists in the database.
+ /// Returns whether a table with the specified exists in the database.
///
/// The name of the table.
/// true if the table exists; otherwise false.
///
- ///
+ ///
/// if (schemaHelper.TableExist("MyTable"))
/// {
/// // do something when the table exists
/// }
///
///
- public bool TableExists(string tableName)
- {
- return SqlSyntax.DoesTableExist(_database, tableName);
- }
+ public bool TableExists(string tableName) => SqlSyntax.DoesTableExist(_database, tableName);
///
- /// Returns whether the table for the specified exists in the database.
+ /// Returns whether the table for the specified exists in the database.
///
/// The type representing the DTO/table.
/// true if the table exists; otherwise false.
///
- ///
+ ///
/// if (schemaHelper.TableExist<MyDto>)
/// {
/// // do something when the table exists
@@ -369,66 +389,67 @@ public bool TableExists(string tableName)
///
///
///
- /// If has been decorated with an , the name from that
- /// attribute will be used for the table name. If the attribute is not present, the name
- /// will be used instead.
+ /// If has been decorated with an , the name from that
+ /// attribute will be used for the table name. If the attribute is not present, the name
+ /// will be used instead.
///
public bool TableExists()
{
- var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
+ TableDefinition table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax);
return table != null && TableExists(table.Name);
}
///
- /// Creates a new table in the database based on the type of .
+ /// Creates a new table in the database based on the type of .
///
/// The type representing the DTO/table.
/// Whether the table should be overwritten if it already exists.
///
- /// If has been decorated with an , the name from that
- /// attribute will be used for the table name. If the attribute is not present, the name
- /// will be used instead.
- ///
- /// If a table with the same name already exists, the parameter will determine
- /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will
- /// not do anything if the parameter is false.
+ /// If has been decorated with an , the name from that
+ /// attribute will be used for the table name. If the attribute is not present, the name
+ /// will be used instead.
+ /// If a table with the same name already exists, the parameter will determine
+ /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will
+ /// not do anything if the parameter is false.
///
internal void CreateTable(bool overwrite = false)
where T : new()
{
- var tableType = typeof(T);
- CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _loggerFactory.CreateLogger(), _umbracoVersion));
+ Type tableType = typeof(T);
+ CreateTable(overwrite, tableType,
+ new DatabaseDataCreator(_database, _loggerFactory.CreateLogger(),
+ _umbracoVersion));
}
///
- /// Creates a new table in the database for the specified .
+ /// Creates a new table in the database for the specified .
///
/// Whether the table should be overwritten if it already exists.
/// The representing the table.
///
///
- /// If has been decorated with an , the name from
- /// that attribute will be used for the table name. If the attribute is not present, the name
- /// will be used instead.
- ///
- /// If a table with the same name already exists, the parameter will determine
- /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will
- /// not do anything if the parameter is false.
- ///
- /// This need to execute as part of a transaction.
+ /// If has been decorated with an , the name from
+ /// that attribute will be used for the table name. If the attribute is not present, the name
+ /// will be used instead.
+ /// If a table with the same name already exists, the parameter will determine
+ /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will
+ /// not do anything if the parameter is false.
+ /// This need to execute as part of a transaction.
///
internal void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation)
{
if (!_database.InTransaction)
+ {
throw new InvalidOperationException("Database is not in a transaction.");
+ }
- var tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax);
+ TableDefinition tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax);
var tableName = tableDefinition.Name;
var createSql = SqlSyntax.Format(tableDefinition);
var createPrimaryKeySql = SqlSyntax.FormatPrimaryKey(tableDefinition);
- var foreignSql = SqlSyntax.Format(tableDefinition.ForeignKeys);
- var indexSql = SqlSyntax.Format(tableDefinition.Indexes);
+ List foreignSql = SqlSyntax.Format(tableDefinition.ForeignKeys);
+ List indexSql = SqlSyntax.Format(tableDefinition.Indexes);
var tableExist = TableExists(tableName);
if (overwrite && tableExist)
@@ -458,7 +479,9 @@ internal void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator da
}
if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity))
+ {
_database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON "));
+ }
//Call the NewTable-event to trigger the insert of base/default data
//OnNewTable(tableName, _db, e, _logger);
@@ -466,7 +489,9 @@ internal void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator da
dataCreation.InitializeBaseData(tableName);
if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity))
+ {
_database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;"));
+ }
//Loop through index statements and execute sql
foreach (var sql in indexSql)
@@ -489,23 +514,22 @@ internal void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator da
else
{
_logger.LogInformation("New table {TableName} was created", tableName);
-
}
}
///
- /// Drops the table for the specified .
+ /// Drops the table for the specified .
///
/// The type representing the DTO/table.
///
- ///
+ ///
/// schemaHelper.DropTable<MyDto>);
///
///
///
- /// If has been decorated with an , the name from that
- /// attribute will be used for the table name. If the attribute is not present, the name
- /// will be used instead.
+ /// If has been decorated with an , the name from that
+ /// attribute will be used for the table name. If the attribute is not present, the name
+ /// will be used instead.
///
public void DropTable(string tableName)
{
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index ea629e5c3afb..76a67fd33078 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -1,4 +1,5 @@
using System;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Semver;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.Common;
@@ -12,47 +13,88 @@
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_7_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_9_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0;
+using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_1_0;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
{
///
- /// Represents the Umbraco CMS migration plan.
+ /// Represents the Umbraco CMS migration plan.
///
///
public class UmbracoPlan : MigrationPlan
{
- private readonly IUmbracoVersion _umbracoVersion;
private const string InitPrefix = "{init-";
private const string InitSuffix = "}";
+ private readonly IUmbracoVersion _umbracoVersion;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The Umbraco version.
public UmbracoPlan(IUmbracoVersion umbracoVersion)
- : base(Core.Constants.Conventions.Migrations.UmbracoUpgradePlanName)
+ : base(Constants.Conventions.Migrations.UmbracoUpgradePlanName)
{
_umbracoVersion = umbracoVersion;
DefinePlan();
}
+ ///
+ ///
+ /// The default initial state in plans is string.Empty.
+ ///
+ /// When upgrading from version 7, we want to use specific initial states
+ /// that are e.g. "{init-7.9.3}", "{init-7.11.1}", etc. so we can chain the proper
+ /// migrations.
+ ///
+ ///
+ /// This is also where we detect the current version, and reject invalid
+ /// upgrades (from a tool old version, or going back in time, etc).
+ ///
+ ///
+ public override string InitialState
+ {
+ get
+ {
+ SemVersion currentVersion = _umbracoVersion.SemanticVersion;
+
+ // only from 8.0.0 and above
+ var minVersion = new SemVersion(8);
+ if (currentVersion < minVersion)
+ {
+ throw new InvalidOperationException(
+ $"Version {currentVersion} cannot be migrated to {_umbracoVersion.SemanticVersion}."
+ + $" Please upgrade first to at least {minVersion}.");
+ }
+
+ // Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes,
+ // and we don't want users to workaround my putting in version 7.14.0 them self.
+ if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16))
+ {
+ return GetInitState(minVersion);
+ }
+
+ // initial state is eg "{init-7.14.0}"
+ return GetInitState(currentVersion);
+ }
+ }
+
///
- /// Gets the initial state corresponding to a version.
+ /// Gets the initial state corresponding to a version.
///
/// The version.
///
- /// The initial state.
+ /// The initial state.
///
private static string GetInitState(SemVersion version) => InitPrefix + version + InitSuffix;
///
- /// Tries to extract a version from an initial state.
+ /// Tries to extract a version from an initial state.
///
/// The state.
/// The version.
///
- /// true when the state contains a version; otherwise, false.D
+ /// true when the state contains a version; otherwise, false.D
///
private static bool TryGetInitStateVersion(string state, out string version)
{
@@ -66,51 +108,21 @@ private static bool TryGetInitStateVersion(string state, out string version)
return false;
}
- ///
- ///
- /// The default initial state in plans is string.Empty.
- /// When upgrading from version 7, we want to use specific initial states
- /// that are e.g. "{init-7.9.3}", "{init-7.11.1}", etc. so we can chain the proper
- /// migrations.
- /// This is also where we detect the current version, and reject invalid
- /// upgrades (from a tool old version, or going back in time, etc).
- ///
- public override string InitialState
- {
- get
- {
- var currentVersion = _umbracoVersion.SemanticVersion;
-
- // only from 8.0.0 and above
- var minVersion = new SemVersion(8, 0);
- if (currentVersion < minVersion)
- throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {_umbracoVersion.SemanticVersion}."
- + $" Please upgrade first to at least {minVersion}.");
-
- // Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes,
- // and we don't want users to workaround my putting in version 7.14.0 them self.
- if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16))
- return GetInitState(minVersion);
-
- // initial state is eg "{init-7.14.0}"
- return GetInitState(currentVersion);
- }
- }
-
///
public override void ThrowOnUnknownInitialState(string state)
{
if (TryGetInitStateVersion(state, out var initVersion))
{
- throw new InvalidOperationException($"Version {_umbracoVersion.SemanticVersion} does not support migrating from {initVersion}."
- + $" Please verify which versions support migrating from {initVersion}.");
+ throw new InvalidOperationException(
+ $"Version {_umbracoVersion.SemanticVersion} does not support migrating from {initVersion}."
+ + $" Please verify which versions support migrating from {initVersion}.");
}
base.ThrowOnUnknownInitialState(state);
}
///
- /// Defines the plan.
+ /// Defines the plan.
///
protected void DefinePlan()
{
@@ -131,7 +143,7 @@ protected void DefinePlan()
// plan starts at 7.14.0 (anything before 7.14.0 is not supported)
- From(GetInitState(new SemVersion(7, 14, 0)));
+ From(GetInitState(new SemVersion(7, 14)));
// begin migrating from v7 - remove all keys and indexes
To("{B36B9ABD-374E-465B-9C5F-26AB0D39326F}");
@@ -146,14 +158,15 @@ protected void DefinePlan()
To("{9DF05B77-11D1-475C-A00A-B656AF7E0908}");
To("{6FE3EF34-44A0-4992-B379-B40BC4EF1C4D}");
To("{7F59355A-0EC9-4438-8157-EB517E6D2727}");
- ToWithReplace("{941B2ABA-2D06-4E04-81F5-74224F1DB037}", "{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}"); // kill AddVariationTable1
+ ToWithReplace("{941B2ABA-2D06-4E04-81F5-74224F1DB037}",
+ "{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}"); // kill AddVariationTable1
To("{A7540C58-171D-462A-91C5-7A9AA5CB8BFD}");
Merge()
.To("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}")
- .With()
+ .With()
.To("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}")
- .As("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}");
+ .As("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}");
To("{1350617A-4930-4D61-852F-E3AA9E692173}");
To("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}");
@@ -175,9 +188,9 @@ protected void DefinePlan()
Merge()
.To("{CDBEDEE4-9496-4903-9CF2-4104E00FF960}")
- .With()
+ .With()
.To("{940FD19A-00A8-4D5C-B8FF-939143585726}")
- .As("{0576E786-5C30-4000-B969-302B61E90CA3}");
+ .As("{0576E786-5C30-4000-B969-302B61E90CA3}");
To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}");
To("{DF470D86-E5CA-42AC-9780-9D28070E25F9}");
@@ -241,13 +254,15 @@ protected void DefinePlan()
To("{3D8DADEF-0FDA-4377-A5F0-B52C2110E8F2}");
To("{1303BDCF-2295-4645-9526-2F32E8B35ABD}");
To("{5060F3D2-88BE-4D30-8755-CF51F28EAD12}");
- To("{A2686B49-A082-4B22-97FD-AAB154D46A57}"); // Re-run this migration to make sure it has executed to account for migrations going out of sync between versions.
+ To(
+ "{A2686B49-A082-4B22-97FD-AAB154D46A57}"); // Re-run this migration to make sure it has executed to account for migrations going out of sync between versions.
// TO 9.0.0-rc4
- To("5E02F241-5253-403D-B5D3-7DB00157E20F"); // Jaddie: This GUID is missing the { }, although this likely can't be changed now as it will break installs going forwards
-
- // TO 9.0.0
+ To(
+ "5E02F241-5253-403D-B5D3-7DB00157E20F"); // Jaddie: This GUID is missing the { }, although this likely can't be changed now as it will break installs going forwards
+ // TO 9.1.0
+ To("{8BAF5E6C-DCB7-41AE-824F-4215AE4F1F98}");
}
}
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_1_0/AddContentVersionCleanupFeature.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_1_0/AddContentVersionCleanupFeature.cs
new file mode 100644
index 000000000000..aa0d4472e813
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_1_0/AddContentVersionCleanupFeature.cs
@@ -0,0 +1,28 @@
+using Umbraco.Cms.Infrastructure.Migrations;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_1_0
+{
+ class AddContentVersionCleanupFeature : MigrationBase
+ {
+ public AddContentVersionCleanupFeature(IMigrationContext context)
+ : base(context) { }
+
+ ///
+ /// The conditionals are useful to enable the same migration to be used in multiple
+ /// migration paths x.x -> 8.18 and x.x -> 9.x
+ ///
+ protected override void Migrate()
+ {
+ var tables = SqlSyntax.GetTablesInSchema(Context.Database);
+ if (!tables.InvariantContains(ContentVersionCleanupPolicyDto.TableName))
+ {
+ Create.Table().Do();
+ }
+
+ var columns = SqlSyntax.GetColumnsInSchema(Context.Database);
+ AddColumnIfNotExists(columns, "preventCleanup");
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionCleanupPolicyDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionCleanupPolicyDto.cs
new file mode 100644
index 000000000000..4b2faa166f5b
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionCleanupPolicyDto.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Data;
+using NPoco;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
+
+namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
+{
+ [TableName(TableName)]
+ [PrimaryKey("contentTypeId", AutoIncrement = false)]
+ [ExplicitColumns]
+ internal class ContentVersionCleanupPolicyDto
+ {
+ public const string TableName = Constants.DatabaseSchema.Tables.ContentVersionCleanupPolicy;
+
+ [Column("contentTypeId")]
+ [ForeignKey(typeof(ContentTypeDto), Column = "nodeId", OnDelete = Rule.Cascade)]
+ public int ContentTypeId { get; set; }
+
+ [Column("preventCleanup")]
+ public bool PreventCleanup { get; set; }
+
+ [Column("keepAllVersionsNewerThanDays")]
+ [NullSetting(NullSetting = NullSettings.Null)]
+ public int? KeepAllVersionsNewerThanDays { get; set; }
+
+ [Column("keepLatestVersionPerDayForDays")]
+ [NullSetting(NullSetting = NullSettings.Null)]
+ public int? KeepLatestVersionPerDayForDays { get; set; }
+
+ [Column("updated")]
+ public DateTime Updated { get; set; }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs
index 53e90859d9c3..63f2802af625 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs
@@ -49,5 +49,9 @@ public class ContentVersionDto
[ResultColumn]
[Reference(ReferenceType.OneToOne, ColumnName = "NodeId", ReferenceMemberName = "NodeId")]
public ContentDto ContentDto { get; set; }
+
+ [Column("preventCleanup")]
+ [Constraint(Default = "0")]
+ public bool PreventCleanup { get; set; }
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
index e171a3a54f77..2a14a745c9a1 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
@@ -6,31 +6,35 @@
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Exceptions;
using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Persistence.Factories;
using Umbraco.Extensions;
+using Enumerable = System.Linq.Enumerable;
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
///
- /// Implements .
+ /// Implements .
///
internal class ContentTypeCommonRepository : IContentTypeCommonRepository
{
- private const string CacheKey = "Umbraco.Core.Persistence.Repositories.Implement.ContentTypeCommonRepository::AllTypes";
+ private const string CacheKey =
+ "Umbraco.Core.Persistence.Repositories.Implement.ContentTypeCommonRepository::AllTypes";
private readonly AppCaches _appCaches;
- private readonly IShortStringHelper _shortStringHelper;
private readonly IScopeAccessor _scopeAccessor;
+ private readonly IShortStringHelper _shortStringHelper;
private readonly ITemplateRepository _templateRepository;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public ContentTypeCommonRepository(IScopeAccessor scopeAccessor, ITemplateRepository templateRepository, AppCaches appCaches, IShortStringHelper shortStringHelper)
+ public ContentTypeCommonRepository(IScopeAccessor scopeAccessor, ITemplateRepository templateRepository,
+ AppCaches appCaches, IShortStringHelper shortStringHelper)
{
_scopeAccessor = scopeAccessor;
_templateRepository = templateRepository;
@@ -40,83 +44,98 @@ public ContentTypeCommonRepository(IScopeAccessor scopeAccessor, ITemplateReposi
private IScope AmbientScope => _scopeAccessor.AmbientScope;
private IUmbracoDatabase Database => AmbientScope.Database;
+
private ISqlContext SqlContext => AmbientScope.SqlContext;
- private Sql Sql() => SqlContext.Sql();
//private Sql Sql(string sql, params object[] args) => SqlContext.Sql(sql, args);
//private ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax;
//private IQuery Query() => SqlContext.Query();
///
- public IEnumerable GetAllTypes()
- {
+ public IEnumerable GetAllTypes() =>
// use a 5 minutes sliding cache - same as FullDataSet cache policy
- return _appCaches.RuntimeCache.GetCacheItem(CacheKey, GetAllTypesInternal, TimeSpan.FromMinutes(5), true);
- }
+ _appCaches.RuntimeCache.GetCacheItem(CacheKey, GetAllTypesInternal, TimeSpan.FromMinutes(5), true);
///
- public void ClearCache()
- {
- _appCaches.RuntimeCache.Clear(CacheKey);
- }
+ public void ClearCache() => _appCaches.RuntimeCache.Clear(CacheKey);
+
+ private Sql Sql() => SqlContext.Sql();
private IEnumerable GetAllTypesInternal()
{
var contentTypes = new Dictionary();
// get content types
- var sql1 = Sql()
+ Sql sql1 = Sql()
.Select(r => r.Select(x => x.NodeDto))
.From()
.InnerJoin().On((ct, n) => ct.NodeId == n.NodeId)
.OrderBy(x => x.NodeId);
- var contentTypeDtos = Database.Fetch(sql1);
+ List contentTypeDtos = Database.Fetch(sql1);
// get allowed content types
- var sql2 = Sql()
+ Sql sql2 = Sql()
.Select()
.From()
.OrderBy(x => x.Id);
- var allowedDtos = Database.Fetch(sql2);
+ List allowedDtos = Database.Fetch(sql2);
// prepare
// note: same alias could be used for media, content... but always different ids = ok
- var aliases = contentTypeDtos.ToDictionary(x => x.NodeId, x => x.Alias);
+ var aliases = Enumerable.ToDictionary(contentTypeDtos, x => x.NodeId, x => x.Alias);
// create
var allowedDtoIx = 0;
- foreach (var contentTypeDto in contentTypeDtos)
+ foreach (ContentTypeDto contentTypeDto in contentTypeDtos)
{
// create content type
IContentTypeComposition contentType;
- if (contentTypeDto.NodeDto.NodeObjectType == Cms.Core.Constants.ObjectTypes.MediaType)
+ if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MediaType)
+ {
contentType = ContentTypeFactory.BuildMediaTypeEntity(_shortStringHelper, contentTypeDto);
- else if (contentTypeDto.NodeDto.NodeObjectType == Cms.Core.Constants.ObjectTypes.DocumentType)
+ }
+ else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.DocumentType)
+ {
contentType = ContentTypeFactory.BuildContentTypeEntity(_shortStringHelper, contentTypeDto);
- else if (contentTypeDto.NodeDto.NodeObjectType == Cms.Core.Constants.ObjectTypes.MemberType)
+ }
+ else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
+ {
contentType = ContentTypeFactory.BuildMemberTypeEntity(_shortStringHelper, contentTypeDto);
- else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
+ }
+ else
+ {
+ throw new PanicException(
+ $"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
+ }
+
contentTypes.Add(contentType.Id, contentType);
// map allowed content types
var allowedContentTypes = new List();
while (allowedDtoIx < allowedDtos.Count && allowedDtos[allowedDtoIx].Id == contentTypeDto.NodeId)
{
- var allowedDto = allowedDtos[allowedDtoIx];
- if (!aliases.TryGetValue(allowedDto.AllowedId, out var alias)) continue;
- allowedContentTypes.Add(new ContentTypeSort(new Lazy(() => allowedDto.AllowedId), allowedDto.SortOrder, alias));
+ ContentTypeAllowedContentTypeDto allowedDto = allowedDtos[allowedDtoIx];
+ if (!aliases.TryGetValue(allowedDto.AllowedId, out var alias))
+ {
+ continue;
+ }
+
+ allowedContentTypes.Add(new ContentTypeSort(new Lazy(() => allowedDto.AllowedId),
+ allowedDto.SortOrder, alias));
allowedDtoIx++;
}
+
contentType.AllowedContentTypes = allowedContentTypes;
}
MapTemplates(contentTypes);
MapComposition(contentTypes);
MapGroupsAndProperties(contentTypes);
+ MapHistoryCleanup(contentTypes);
// finalize
- foreach (var contentType in contentTypes.Values)
+ foreach (IContentTypeComposition contentType in contentTypes.Values)
{
contentType.ResetDirtyProperties(false);
}
@@ -124,36 +143,79 @@ private IEnumerable GetAllTypesInternal()
return contentTypes.Values;
}
+ private void MapHistoryCleanup(Dictionary contentTypes)
+ {
+ // get templates
+ Sql sql1 = Sql()
+ .Select()
+ .From()
+ .OrderBy(x => x.ContentTypeId);
+
+ var contentVersionCleanupPolicyDtos = Database.Fetch(sql1);
+
+ var contentVersionCleanupPolicyDictionary =
+ contentVersionCleanupPolicyDtos.ToDictionary(x => x.ContentTypeId);
+ foreach (IContentTypeComposition c in contentTypes.Values)
+ {
+ if (!(c is ContentType contentType))
+ {
+ continue;
+ }
+
+ var historyCleanup = new HistoryCleanup();
+
+ if (contentVersionCleanupPolicyDictionary.TryGetValue(contentType.Id, out var versionCleanup))
+ {
+ historyCleanup.PreventCleanup = versionCleanup.PreventCleanup;
+ historyCleanup.KeepAllVersionsNewerThanDays = versionCleanup.KeepAllVersionsNewerThanDays;
+ historyCleanup.KeepLatestVersionPerDayForDays = versionCleanup.KeepLatestVersionPerDayForDays;
+ }
+
+ contentType.HistoryCleanup = historyCleanup;
+ }
+ }
+
private void MapTemplates(Dictionary contentTypes)
{
// get templates
- var sql1 = Sql()
+ Sql sql1 = Sql()
.Select()
.From()
.OrderBy(x => x.ContentTypeNodeId);
- var templateDtos = Database.Fetch(sql1);
+ List templateDtos = Database.Fetch(sql1);
//var templates = templateRepository.GetMany(templateDtos.Select(x => x.TemplateNodeId).ToArray()).ToDictionary(x => x.Id, x => x);
- var templates = _templateRepository.GetMany().ToDictionary(x => x.Id, x => x);
+ var templates = Enumerable.ToDictionary(_templateRepository.GetMany(), x => x.Id, x => x);
var templateDtoIx = 0;
- foreach (var c in contentTypes.Values)
+ foreach (IContentTypeComposition c in contentTypes.Values)
{
- if (!(c is IContentType contentType)) continue;
+ if (!(c is IContentType contentType))
+ {
+ continue;
+ }
// map allowed templates
var allowedTemplates = new List();
var defaultTemplateId = 0;
- while (templateDtoIx < templateDtos.Count && templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id)
+ while (templateDtoIx < templateDtos.Count &&
+ templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id)
{
- var allowedDto = templateDtos[templateDtoIx];
+ ContentTypeTemplateDto allowedDto = templateDtos[templateDtoIx];
templateDtoIx++;
- if (!templates.TryGetValue(allowedDto.TemplateNodeId, out var template)) continue;
+ if (!templates.TryGetValue(allowedDto.TemplateNodeId, out ITemplate template))
+ {
+ continue;
+ }
+
allowedTemplates.Add(template);
if (allowedDto.IsDefault)
+ {
defaultTemplateId = template.Id;
+ }
}
+
contentType.AllowedTemplates = allowedTemplates;
contentType.DefaultTemplateId = defaultTemplateId;
}
@@ -162,24 +224,28 @@ private void MapTemplates(Dictionary contentTypes)
private void MapComposition(IDictionary contentTypes)
{
// get parent/child
- var sql1 = Sql()
+ Sql sql1 = Sql()
.Select()
.From()
.OrderBy(x => x.ChildId);
- var compositionDtos = Database.Fetch