diff --git a/build/templates/UmbracoProject/appsettings.json b/build/templates/UmbracoProject/appsettings.json
index feb6b07d9531..99e877812cee 100644
--- a/build/templates/UmbracoProject/appsettings.json
+++ b/build/templates/UmbracoProject/appsettings.json
@@ -17,6 +17,7 @@
"CMS": {
//#if (HasNoNodesViewPath || UseHttpsRedirect)
"Global": {
+ "SanitizeTinyMce": true,
//#if (!HasNoNodesViewPath && UseHttpsRedirect)
"UseHttps": true
//#elseif (UseHttpsRedirect)
@@ -25,10 +26,16 @@
//#if (HasNoNodesViewPath)
"NoNodesViewPath": "NO_NODES_VIEW_PATH_FROM_TEMPLATE"
//#endif
+
},
//#endif
"Hosting": {
"Debug": false
+ },
+ "Content": {
+ "ContentVersionCleanupPolicy": {
+ "EnableCleanup": true
+ }
}
}
}
diff --git a/src/Umbraco.Core/Collections/StackQueue.cs b/src/Umbraco.Core/Collections/StackQueue.cs
index 9bf9c365f077..b760ffe646a5 100644
--- a/src/Umbraco.Core/Collections/StackQueue.cs
+++ b/src/Umbraco.Core/Collections/StackQueue.cs
@@ -3,58 +3,37 @@
namespace Umbraco.Core.Collections
{
///
- /// Collection that can be both a queue and a stack.
+ /// Collection that can be both a queue and a stack.
///
///
public class StackQueue
{
- private readonly LinkedList _linkedList = new LinkedList();
+ private readonly LinkedList _linkedList = new();
- public void Clear()
- {
- _linkedList.Clear();
- }
+ public int Count => _linkedList.Count;
- public void Push(T obj)
- {
- _linkedList.AddFirst(obj);
- }
+ public void Clear() => _linkedList.Clear();
- public void Enqueue(T obj)
- {
- _linkedList.AddFirst(obj);
- }
+ public void Push(T obj) => _linkedList.AddFirst(obj);
+
+ public void Enqueue(T obj) => _linkedList.AddFirst(obj);
public T Pop()
{
- var obj = _linkedList.First.Value;
+ T obj = _linkedList.First.Value;
_linkedList.RemoveFirst();
return obj;
}
public T Dequeue()
{
- var obj = _linkedList.Last.Value;
+ T obj = _linkedList.Last.Value;
_linkedList.RemoveLast();
return obj;
}
- public T PeekStack()
- {
- return _linkedList.First.Value;
- }
+ public T PeekStack() => _linkedList.First.Value;
- public T PeekQueue()
- {
- return _linkedList.Last.Value;
- }
-
- public int Count
- {
- get
- {
- return _linkedList.Count;
- }
- }
+ public T PeekQueue() => _linkedList.Last.Value;
}
}
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/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
index 7799fec5eacd..c88083027488 100644
--- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
@@ -28,6 +28,7 @@ public class GlobalSettings
internal const bool StaticDisableElectionForSingleServer = false;
internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml";
internal const string StaticSqlWriteLockTimeOut = "00:00:05";
+ internal const bool StaticSanitizeTinyMce = false;
///
/// Gets or sets a value for the reserved URLs.
@@ -157,6 +158,12 @@ public class GlobalSettings
///
public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host);
+ ///
+ /// Gets a value indicating whether TinyMCE scripting sanitization should be applied
+ ///
+ [DefaultValue(StaticSanitizeTinyMce)]
+ public bool SanitizeTinyMce => StaticSanitizeTinyMce;
+
///
/// An int value representing the time in milliseconds to lock the database for a write operation
///
diff --git a/src/Umbraco.Core/Constants-Sql.cs b/src/Umbraco.Core/Constants-Sql.cs
new file mode 100644
index 000000000000..b57861c92ac8
--- /dev/null
+++ b/src/Umbraco.Core/Constants-Sql.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Cms.Core
+{
+ public static partial class Constants
+ {
+ public static class Sql
+ {
+ ///
+ /// The maximum amount of parameters that can be used in a query.
+ ///
+ ///
+ /// The actual limit is 2100
+ /// (https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server),
+ /// but we want to ensure there's room for additional parameters if this value is used to create groups/batches.
+ ///
+ public const int MaxParameterCount = 2000;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs
index eeb291c41faf..a3e861e18041 100644
--- a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs
+++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs
@@ -79,7 +79,7 @@ protected async Task CheckForHeader()
var success = false;
// Access the site home page and check for the click-jack protection header or meta tag
- Uri url = _hostingEnvironment.ApplicationMainUrl;
+ var url = _hostingEnvironment.ApplicationMainUrl.GetLeftPart(UriPartial.Authority);
try
{
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/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
index 238065718002..e4d101ff062d 100644
--- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
+++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs
@@ -251,7 +251,7 @@ private void Map(IUserGroup source, UserGroupDisplay target, MapperContext conte
// the entity service due to too many Sql parameters.
var list = new List();
- foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000))
+ foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(Constants.Sql.MaxParameterCount))
list.AddRange(_entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray()));
contentEntities = list.ToArray();
}
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 734ed2261d18..56cb1e6d01eb 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -1,61 +1,65 @@
-
- netstandard2.0
- Umbraco.Cms.Core
- Umbraco CMS
- Umbraco.Cms.Core
- Umbraco CMS Core
- Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
- Umbraco CMS
-
+
+ netstandard2.0
+ Umbraco.Cms.Core
+ Umbraco CMS
+ Umbraco.Cms.Core
+ Umbraco CMS Core
+ Contains the core assembly needed to run Umbraco Cms. This package only contains the assembly, and can be used for package development. Use the template in the Umbraco.Templates package to setup Umbraco
+ Umbraco CMS
+
-
- bin\Release\Umbraco.Core.xml
-
+
+ bin\Release\Umbraco.Core.xml
+
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
- all
-
-
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ all
+
+
-
-
- <_Parameter1>Umbraco.Tests
-
-
- <_Parameter1>Umbraco.Tests.Common
-
-
- <_Parameter1>Umbraco.Tests.UnitTests
-
-
- <_Parameter1>Umbraco.Tests.Benchmarks
-
-
- <_Parameter1>Umbraco.Tests.Integration
-
-
- <_Parameter1>DynamicProxyGenAssembly2
-
-
+
+
+ <_Parameter1>Umbraco.Tests
+
+
+ <_Parameter1>Umbraco.Tests.Common
+
+
+ <_Parameter1>Umbraco.Tests.UnitTests
+
+
+ <_Parameter1>Umbraco.Tests.Benchmarks
+
+
+ <_Parameter1>Umbraco.Tests.Integration
+
+
+ <_Parameter1>DynamicProxyGenAssembly2
+
+
-
-
-
+
+
+
+
+
+
+
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/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs
index 443032c67ae8..f07867cccc83 100644
--- a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs
+++ b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions-Bulk.cs
@@ -1,38 +1,43 @@
using System;
using System.Collections.Generic;
using System.Data;
+using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using NPoco;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Extensions
{
///
- /// Provides extension methods to NPoco Database class.
+ /// Provides extension methods to NPoco Database class.
///
public static partial class NPocoDatabaseExtensions
{
///
- /// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the underlying RetryDbConnection and ProfiledDbTransaction
+ /// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the
+ /// underlying RetryDbConnection and ProfiledDbTransaction
///
///
- /// This is required to use NPoco's own method because we use wrapped DbConnection and DbTransaction instances.
- /// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for bulk inserting of records for
- /// any other database type and in which case will just insert records one at a time.
- /// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own BulkInsertRecords methods
- /// do not handle this scenario.
+ /// This is required to use NPoco's own method because we use
+ /// wrapped DbConnection and DbTransaction instances.
+ /// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for
+ /// bulk inserting of records for
+ /// any other database type and in which case will just insert records one at a time.
+ /// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own
+ /// BulkInsertRecords methods
+ /// do not handle this scenario.
///
public static void ConfigureNPocoBulkExtensions()
{
-
SqlBulkCopyHelper.SqlConnectionResolver = dbConn => GetTypedConnection(dbConn);
SqlBulkCopyHelper.SqlTransactionResolver = dbTran => GetTypedTransaction(dbTran);
}
///
- /// Creates bulk-insert commands.
+ /// Creates bulk-insert commands.
///
/// The type of the records.
/// The database.
@@ -40,17 +45,22 @@ public static void ConfigureNPocoBulkExtensions()
/// The sql commands to execute.
internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase database, T[] records)
{
- if (database?.Connection == null) throw new ArgumentException("Null database?.connection.", nameof(database));
+ if (database?.Connection == null)
+ {
+ throw new ArgumentException("Null database?.connection.", nameof(database));
+ }
- var pocoData = database.PocoDataFactory.ForType(typeof(T));
+ PocoData pocoData = database.PocoDataFactory.ForType(typeof(T));
// get columns to include, = number of parameters per row
- var columns = pocoData.Columns.Where(c => IncludeColumn(pocoData, c)).ToArray();
+ KeyValuePair[] columns =
+ pocoData.Columns.Where(c => IncludeColumn(pocoData, c)).ToArray();
var paramsPerRecord = columns.Length;
// format columns to sql
var tableName = database.DatabaseType.EscapeTableName(pocoData.TableInfo.TableName);
- var columnNames = string.Join(", ", columns.Select(c => tableName + "." + database.DatabaseType.EscapeSqlIdentifier(c.Key)));
+ var columnNames = string.Join(", ",
+ columns.Select(c => tableName + "." + database.DatabaseType.EscapeSqlIdentifier(c.Key)));
// example:
// assume 4168 records, each record containing 8 fields, ie 8 command parameters
@@ -58,7 +68,9 @@ internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase
// Math.Floor(2100 / 8) = 262 record per command
// 4168 / 262 = 15.908... = there will be 16 command in total
// (if we have disabled db parameters, then all records will be included, in only one command)
- var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor(2000.00 / paramsPerRecord));
+ var recordsPerCommand = paramsPerRecord == 0
+ ? int.MaxValue
+ : Convert.ToInt32(Math.Floor((double)Constants.Sql.MaxParameterCount / paramsPerRecord));
var commandsCount = Convert.ToInt32(Math.Ceiling((double)records.Length / recordsPerCommand));
var commands = new IDbCommand[commandsCount];
@@ -67,23 +79,27 @@ internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase
var prefix = database.DatabaseType.GetParameterPrefix(database.ConnectionString);
for (var commandIndex = 0; commandIndex < commandsCount; commandIndex++)
{
- var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty);
+ DbCommand command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty);
var parameterIndex = 0;
var commandRecords = Math.Min(recordsPerCommand, recordsLeftToInsert);
var recordsValues = new string[commandRecords];
- for (var commandRecordIndex = 0; commandRecordIndex < commandRecords; commandRecordIndex++, recordsIndex++, recordsLeftToInsert--)
+ for (var commandRecordIndex = 0;
+ commandRecordIndex < commandRecords;
+ commandRecordIndex++, recordsIndex++, recordsLeftToInsert--)
{
- var record = records[recordsIndex];
+ T record = records[recordsIndex];
var recordValues = new string[columns.Length];
for (var columnIndex = 0; columnIndex < columns.Length; columnIndex++)
{
database.AddParameter(command, columns[columnIndex].Value.GetValue(record));
recordValues[columnIndex] = prefix + parameterIndex++;
}
+
recordsValues[commandRecordIndex] = "(" + string.Join(",", recordValues) + ")";
}
- command.CommandText = $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", recordsValues)}";
+ command.CommandText =
+ $"INSERT INTO {tableName} ({columnNames}) VALUES {string.Join(", ", recordsValues)}";
commands[commandIndex] = command;
}
@@ -91,19 +107,14 @@ internal static IDbCommand[] GenerateBulkInsertCommands(this IUmbracoDatabase
}
///
- /// Determines whether a column should be part of a bulk-insert.
+ /// Determines whether a column should be part of a bulk-insert.
///
/// The PocoData object corresponding to the record's type.
/// The column.
/// A value indicating whether the column should be part of the bulk-insert.
/// Columns that are primary keys and auto-incremental, or result columns, are excluded from bulk-inserts.
- public static bool IncludeColumn(PocoData pocoData, KeyValuePair column)
- {
- return column.Value.ResultColumn == false
- && (pocoData.TableInfo.AutoIncrement == false || column.Key != pocoData.TableInfo.PrimaryKey);
- }
-
-
-
+ public static bool IncludeColumn(PocoData pocoData, KeyValuePair column) =>
+ column.Value.ResultColumn == false
+ && (pocoData.TableInfo.AutoIncrement == false || column.Key != pocoData.TableInfo.PrimaryKey);
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
index 16c411c772a5..e3685dd32ccf 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
@@ -3,6 +3,7 @@
using System.Linq;
using Microsoft.Extensions.Logging;
using NPoco;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Querying;
@@ -16,26 +17,47 @@
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
///
- /// Represents the NPoco implementation of .
+ /// Represents the NPoco implementation of .
///
internal class AuditEntryRepository : EntityRepositoryBase, IAuditEntryRepository
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
public AuditEntryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
- { }
+ {
+ }
+
+ ///
+ public IEnumerable GetPage(long pageIndex, int pageCount, out long records)
+ {
+ Sql sql = Sql()
+ .Select()
+ .From()
+ .OrderByDescending(x => x.EventDateUtc);
+
+ Page page = Database.Page(pageIndex + 1, pageCount, sql);
+ records = page.TotalItems;
+ return page.Items.Select(AuditEntryFactory.BuildEntity);
+ }
+
+ ///
+ public bool IsAvailable()
+ {
+ var tables = SqlSyntax.GetTablesInSchema(Database).ToArray();
+ return tables.InvariantContains(Constants.DatabaseSchema.Tables.AuditEntry);
+ }
///
protected override IAuditEntry PerformGet(int id)
{
- var sql = Sql()
+ Sql sql = Sql()
.Select()
.From()
.Where(x => x.Id == id);
- var dto = Database.FirstOrDefault(sql);
+ AuditEntryDto dto = Database.FirstOrDefault(sql);
return dto == null ? null : AuditEntryFactory.BuildEntity(dto);
}
@@ -44,7 +66,7 @@ protected override IEnumerable PerformGetAll(params int[] ids)
{
if (ids.Length == 0)
{
- var sql = Sql()
+ Sql sql = Sql()
.Select()
.From();
@@ -53,9 +75,9 @@ protected override IEnumerable PerformGetAll(params int[] ids)
var entries = new List();
- foreach (var group in ids.InGroupsOf(2000))
+ foreach (IEnumerable group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
{
- var sql = Sql()
+ Sql sql = Sql()
.Select()
.From