diff --git a/src/Umbraco.Core/Attempt{T}.cs b/src/Umbraco.Core/AttemptOfT.cs
similarity index 63%
rename from src/Umbraco.Core/Attempt{T}.cs
rename to src/Umbraco.Core/AttemptOfT.cs
index fc8fa3dcc9..d9e2abb186 100644
--- a/src/Umbraco.Core/Attempt{T}.cs
+++ b/src/Umbraco.Core/AttemptOfT.cs
@@ -1,174 +1,128 @@
-using System;
-using Umbraco.Core.Dynamics;
-
-namespace Umbraco.Core
-{
- ///
- /// Represents the result of an operation attempt.
- ///
- /// The type of the attempted operation result.
- [Serializable]
- public struct Attempt
- {
- private readonly bool _success;
- private readonly T _result;
- private readonly Exception _exception;
-
- ///
- /// Gets a value indicating whether this was successful.
- ///
- public bool Success
- {
- get { return _success; }
- }
-
- ///
- /// Gets the exception associated with an unsuccessful attempt.
- ///
- public Exception Exception { get { return _exception; } }
-
- ///
- /// Gets the exception associated with an unsuccessful attempt.
- ///
- /// Keep it for backward compatibility sake.
- [Obsolete(".Error is obsolete, you should use .Exception instead.", false)]
- public Exception Error { get { return _exception; } }
-
- ///
- /// Gets the attempt result.
- ///
- public T Result
- {
- get { return _result; }
- }
-
- // optimize, use a singleton failed attempt
- private static readonly Attempt Failed = new Attempt(false, default(T), null);
-
- ///
- /// Represents an unsuccessful attempt.
- ///
- /// Keep it for backward compatibility sake.
- [Obsolete(".Failed is obsolete, you should use Attempt.Fail() instead.", false)]
- public static readonly Attempt False = Failed;
-
- // private - use Succeed() or Fail() methods to create attempts
- private Attempt(bool success, T result, Exception exception)
- {
- _success = success;
- _result = result;
- _exception = exception;
- }
-
- ///
- /// Initialize a new instance of the struct with a result.
- ///
- /// A value indicating whether the attempt is successful.
- /// The result of the attempt.
- /// Keep it for backward compatibility sake.
- [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)]
- public Attempt(bool success, T result)
- : this(success, result, null)
- { }
-
- ///
- /// Initialize a new instance of the struct representing a failed attempt, with an exception.
- ///
- /// The exception causing the failure of the attempt.
- /// Keep it for backward compatibility sake.
- [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)]
- public Attempt(Exception exception)
- : this(false, default(T), exception)
- { }
-
- ///
- /// Creates a successful attempt.
- ///
- /// The successful attempt.
- public static Attempt Succeed()
- {
- return new Attempt(true, default(T), null);
- }
-
- ///
- /// Creates a successful attempt with a result.
- ///
- /// The result of the attempt.
- /// The successful attempt.
- public static Attempt Succeed(T result)
- {
- return new Attempt(true, result, null);
- }
-
- ///
- /// Creates a failed attempt.
- ///
- /// The failed attempt.
- public static Attempt Fail()
- {
- return Failed;
- }
-
- ///
- /// Creates a failed attempt with an exception.
- ///
- /// The exception causing the failure of the attempt.
- /// The failed attempt.
- public static Attempt Fail(Exception exception)
- {
- return new Attempt(false, default(T), exception);
- }
-
- ///
- /// Creates a failed attempt with a result.
- ///
- /// The result of the attempt.
- /// The failed attempt.
- public static Attempt Fail(T result)
- {
- return new Attempt(false, result, null);
- }
-
- ///
- /// Creates a failed attempt with a result and an exception.
- ///
- /// The result of the attempt.
- /// The exception causing the failure of the attempt.
- /// The failed attempt.
- public static Attempt Fail(T result, Exception exception)
- {
- return new Attempt(false, result, exception);
- }
-
- ///
- /// Creates a successful or a failed attempt.
- ///
- /// A value indicating whether the attempt is successful.
- /// The attempt.
- public static Attempt SucceedIf(bool condition)
- {
- return condition ? new Attempt(true, default(T), null) : Failed;
- }
-
- ///
- /// Creates a successful or a failed attempt, with a result.
- ///
- /// A value indicating whether the attempt is successful.
- /// The result of the attempt.
- /// The attempt.
- public static Attempt SucceedIf(bool condition, T result)
- {
- return new Attempt(condition, result, null);
- }
-
- ///
- /// Implicity operator to check if the attempt was successful without having to access the 'success' property
- ///
- ///
- ///
- public static implicit operator bool(Attempt a)
- {
- return a.Success;
- }
- }
+using System;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Represents the result of an operation attempt.
+ ///
+ /// The type of the attempted operation result.
+ [Serializable]
+ public struct Attempt
+ {
+ ///
+ /// Gets a value indicating whether this was successful.
+ ///
+ public bool Success { get; }
+
+ ///
+ /// Gets the exception associated with an unsuccessful attempt.
+ ///
+ public Exception Exception { get; }
+
+ ///
+ /// Gets the attempt result.
+ ///
+ public T Result { get; }
+
+ // optimize, use a singleton failed attempt
+ private static readonly Attempt Failed = new Attempt(false, default(T), null);
+
+ // private - use Succeed() or Fail() methods to create attempts
+ private Attempt(bool success, T result, Exception exception)
+ {
+ Success = success;
+ Result = result;
+ Exception = exception;
+ }
+
+ ///
+ /// Creates a successful attempt.
+ ///
+ /// The successful attempt.
+ public static Attempt Succeed()
+ {
+ return new Attempt(true, default(T), null);
+ }
+
+ ///
+ /// Creates a successful attempt with a result.
+ ///
+ /// The result of the attempt.
+ /// The successful attempt.
+ public static Attempt Succeed(T result)
+ {
+ return new Attempt(true, result, null);
+ }
+
+ ///
+ /// Creates a failed attempt.
+ ///
+ /// The failed attempt.
+ public static Attempt Fail()
+ {
+ return Failed;
+ }
+
+ ///
+ /// Creates a failed attempt with an exception.
+ ///
+ /// The exception causing the failure of the attempt.
+ /// The failed attempt.
+ public static Attempt Fail(Exception exception)
+ {
+ return new Attempt(false, default(T), exception);
+ }
+
+ ///
+ /// Creates a failed attempt with a result.
+ ///
+ /// The result of the attempt.
+ /// The failed attempt.
+ public static Attempt Fail(T result)
+ {
+ return new Attempt(false, result, null);
+ }
+
+ ///
+ /// Creates a failed attempt with a result and an exception.
+ ///
+ /// The result of the attempt.
+ /// The exception causing the failure of the attempt.
+ /// The failed attempt.
+ public static Attempt Fail(T result, Exception exception)
+ {
+ return new Attempt(false, result, exception);
+ }
+
+ ///
+ /// Creates a successful or a failed attempt.
+ ///
+ /// A value indicating whether the attempt is successful.
+ /// The attempt.
+ public static Attempt SucceedIf(bool condition)
+ {
+ return condition ? new Attempt(true, default(T), null) : Failed;
+ }
+
+ ///
+ /// Creates a successful or a failed attempt, with a result.
+ ///
+ /// A value indicating whether the attempt is successful.
+ /// The result of the attempt.
+ /// The attempt.
+ public static Attempt SucceedIf(bool condition, T result)
+ {
+ return new Attempt(condition, result, null);
+ }
+
+ ///
+ /// Implicity operator to check if the attempt was successful without having to access the 'success' property
+ ///
+ ///
+ ///
+ public static implicit operator bool(Attempt a)
+ {
+ return a.Success;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs
index 560cd4b306..6fd3f01fe2 100644
--- a/src/Umbraco.Core/Constants-ObjectTypes.cs
+++ b/src/Umbraco.Core/Constants-ObjectTypes.cs
@@ -69,6 +69,11 @@ namespace Umbraco.Core
///
public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972";
+ ///
+ /// Guid for a Document object.
+ ///
+ public static readonly Guid DocumentGuid = new Guid(Document);
+
///
/// Guid for a Document Type object.
///
@@ -84,6 +89,11 @@ namespace Umbraco.Core
///
public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C";
+ ///
+ /// Guid for a Media object.
+ ///
+ public static readonly Guid MediaGuid = new Guid(Media);
+
///
/// Guid for the Media Recycle Bin.
///
@@ -143,7 +153,10 @@ namespace Umbraco.Core
///
public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41";
-
+ ///
+ /// Guid for a Lock object.
+ ///
+ public static readonly Guid LockObjectGuid = new Guid(LockObject);
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs
index 82e3a1ff3f..4ac1ff9a70 100644
--- a/src/Umbraco.Core/Constants-System.cs
+++ b/src/Umbraco.Core/Constants-System.cs
@@ -25,9 +25,6 @@
public const int DefaultContentListViewDataTypeId = -95;
public const int DefaultMediaListViewDataTypeId = -96;
public const int DefaultMembersListViewDataTypeId = -97;
-
- // identifiers for lock objects
- public const int ServersLock = -331;
- }
- }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs
index 4f74797f45..e6a100b3e2 100644
--- a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs
+++ b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs
@@ -19,34 +19,33 @@ namespace Umbraco.Core.DependencyInjection
{
public void Compose(IServiceRegistry container)
{
- container.RegisterSingleton();
-
// register a transient messages factory, which will be replaced byt the web
// boot manager when running in a web context
container.Register();
-
+
//the context
container.RegisterSingleton();
-
+
//now the services...
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
- container.RegisterSingleton();
+ container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
+ container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
- container.RegisterSingleton();
+ container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
@@ -84,7 +83,7 @@ namespace Umbraco.Core.DependencyInjection
factory.GetInstance>(),
factory.GetInstance()));
- //TODO: These are replaced in the web project - we need to declare them so that
+ //TODO: These are replaced in the web project - we need to declare them so that
// something is wired up, just not sure this is very nice but will work for now.
container.RegisterSingleton();
container.RegisterSingleton();
diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs
index ca4bbd2719..7818156207 100644
--- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs
+++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs
@@ -17,6 +17,16 @@ namespace Umbraco.Core.Events
Files = new List();
}
+ public RecycleBinEventArgs(Guid nodeObjectType, bool emptiedSuccessfully)
+ : base(false)
+ {
+ AllPropertyData = new Dictionary>();
+ NodeObjectType = nodeObjectType;
+ Ids = new int[0];
+ RecycleBinEmptiedSuccessfully = emptiedSuccessfully;
+ Files = new List();
+ }
+
public RecycleBinEventArgs(Guid nodeObjectType, Dictionary> allPropertyData)
: base(true)
{
@@ -26,6 +36,15 @@ namespace Umbraco.Core.Events
Files = new List();
}
+ public RecycleBinEventArgs(Guid nodeObjectType)
+ : base(true)
+ {
+ AllPropertyData = new Dictionary>();
+ NodeObjectType = nodeObjectType;
+ Ids = new int[0];
+ Files = new List();
+ }
+
///
/// Backwards compatibility constructor
///
diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs
index 4da43d35bb..60f72ed19c 100644
--- a/src/Umbraco.Core/IO/IOHelper.cs
+++ b/src/Umbraco.Core/IO/IOHelper.cs
@@ -4,8 +4,10 @@ using System.Globalization;
using System.Reflection;
using System.IO;
using System.Configuration;
+using System.Linq;
using System.Web;
using System.Text.RegularExpressions;
+using System.Threading.Tasks;
using System.Web.Hosting;
using ICSharpCode.SharpZipLib.Zip;
using Umbraco.Core.Configuration;
@@ -306,7 +308,7 @@ namespace Umbraco.Core.IO
var debugFolder = Path.Combine(binFolder, "debug");
if (Directory.Exists(debugFolder))
return debugFolder;
-#endif
+#endif
var releaseFolder = Path.Combine(binFolder, "release");
if (Directory.Exists(releaseFolder))
return releaseFolder;
@@ -341,7 +343,7 @@ namespace Umbraco.Core.IO
public static void EnsurePathExists(string path)
{
- var absolutePath = IOHelper.MapPath(path);
+ var absolutePath = MapPath(path);
if (Directory.Exists(absolutePath) == false)
Directory.CreateDirectory(absolutePath);
}
@@ -349,14 +351,58 @@ namespace Umbraco.Core.IO
public static void EnsureFileExists(string path, string contents)
{
var absolutePath = IOHelper.MapPath(path);
- if (File.Exists(absolutePath) == false)
+ if (File.Exists(absolutePath)) return;
+
+ using (var writer = File.CreateText(absolutePath))
{
- using (var writer = File.CreateText(absolutePath))
- {
- writer.Write(contents);
- }
+ writer.Write(contents);
}
-
}
+
+ ///
+ /// Deletes all files passed in.
+ ///
+ ///
+ ///
+ ///
+ internal static bool DeleteFiles(IEnumerable files, Action onError = null)
+ {
+ //ensure duplicates are removed
+ files = files.Distinct();
+
+ var allsuccess = true;
+
+ var fs = FileSystemProviderManager.Current.GetFileSystemProvider();
+ Parallel.ForEach(files, file =>
+ {
+ try
+ {
+ if (file.IsNullOrWhiteSpace()) return;
+
+ var relativeFilePath = fs.GetRelativePath(file);
+ if (fs.FileExists(relativeFilePath) == false) return;
+
+ var parentDirectory = Path.GetDirectoryName(relativeFilePath);
+
+ // don't want to delete the media folder if not using directories.
+ if (UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories && parentDirectory != fs.GetRelativePath("/"))
+ {
+ //issue U4-771: if there is a parent directory the recursive parameter should be true
+ fs.DeleteDirectory(parentDirectory, String.IsNullOrEmpty(parentDirectory) == false);
+ }
+ else
+ {
+ fs.DeleteFile(file, true);
+ }
+ }
+ catch (Exception e)
+ {
+ onError?.Invoke(file, e);
+ allsuccess = false;
+ }
+ });
+
+ return allsuccess;
+ }
}
}
diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index b8e96c2793..be24049364 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -17,12 +17,14 @@ namespace Umbraco.Core.Models
private IContentType _contentType;
private ITemplate _template;
private bool _published;
+ private bool? _publishedOriginal;
private string _language;
private DateTime? _releaseDate;
private DateTime? _expireDate;
private int _writer;
private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name.
private bool _permissionsChanged;
+
///
/// Constructor for creating a Content object
///
@@ -31,8 +33,7 @@ namespace Umbraco.Core.Models
/// ContentType for the current Content object
public Content(string name, IContent parent, IContentType contentType)
: this(name, parent, contentType, new PropertyCollection())
- {
- }
+ { }
///
/// Constructor for creating a Content object
@@ -47,6 +48,7 @@ namespace Umbraco.Core.Models
Mandate.ParameterNotNull(contentType, "contentType");
_contentType = contentType;
+ PublishedState = PublishedState.Unpublished;
}
///
@@ -57,8 +59,7 @@ namespace Umbraco.Core.Models
/// ContentType for the current Content object
public Content(string name, int parentId, IContentType contentType)
: this(name, parentId, contentType, new PropertyCollection())
- {
- }
+ { }
///
/// Constructor for creating a Content object
@@ -73,6 +74,7 @@ namespace Umbraco.Core.Models
Mandate.ParameterNotNull(contentType, "contentType");
_contentType = contentType;
+ PublishedState = PublishedState.Unpublished;
}
private static readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo(x => x.Template);
@@ -95,7 +97,13 @@ namespace Umbraco.Core.Models
[DataMember]
public virtual ITemplate Template
{
- get { return _template; }
+ get
+ {
+ if (_template == null)
+ return _contentType.DefaultTemplate;
+
+ return _template;
+ }
set
{
SetPropertyValueAndDetectChanges(o =>
@@ -146,11 +154,19 @@ namespace Umbraco.Core.Models
SetPropertyValueAndDetectChanges(o =>
{
_published = value;
+ _publishedOriginal = _publishedOriginal ?? _published;
+ PublishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
return _published;
}, _published, PublishedSelector);
}
}
+ [IgnoreDataMember]
+ public bool PublishedOriginal
+ {
+ get { return _publishedOriginal ?? false; }
+ }
+
///
/// Language of the data contained within this Content object.
///
@@ -305,12 +321,14 @@ namespace Umbraco.Core.Models
///
public void ChangePublishedState(PublishedState state)
{
- Published = state == PublishedState.Published;
+ if (state == PublishedState.Published || state == PublishedState.Unpublished)
+ throw new ArgumentException("Invalid state.");
+ Published = state == PublishedState.Publishing;
PublishedState = state;
}
[DataMember]
- internal PublishedState PublishedState { get; set; }
+ internal PublishedState PublishedState { get; private set; }
///
/// Gets or sets the unique identifier of the published version, if any.
@@ -322,24 +340,26 @@ namespace Umbraco.Core.Models
/// Gets a value indicating whether the content has a published version.
///
public bool HasPublishedVersion { get { return PublishedVersionGuid != default(Guid); } }
-
- ///
- /// Changes the Trashed state of the content object
- ///
- /// Boolean indicating whether content is trashed (true) or not trashed (false)
- ///
- public override void ChangeTrashedState(bool isTrashed, int parentId = -20)
- {
- Trashed = isTrashed;
- ParentId = parentId;
-
- //If the content is trashed and is published it should be marked as unpublished
- if (isTrashed && Published)
- {
- ChangePublishedState(PublishedState.Unpublished);
- }
- }
+ public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties)
+ {
+ base.ResetDirtyProperties(rememberPreviouslyChangedProperties);
+
+ // take care of the published state
+ switch (PublishedState)
+ {
+ case PublishedState.Saving:
+ case PublishedState.Unpublishing:
+ PublishedState = PublishedState.Unpublished;
+ break;
+ case PublishedState.Publishing:
+ PublishedState = PublishedState.Published;
+ break;
+ }
+
+ _publishedOriginal = _published;
+ }
+
///
/// Method to call when Entity is being updated
///
@@ -377,6 +397,8 @@ namespace Umbraco.Core.Models
property.Version = clone.Version;
}
+ clone.PublishedVersionGuid = Guid.Empty;
+
return clone;
}
diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs
index b3d0f693d9..aff5dc35a3 100644
--- a/src/Umbraco.Core/Models/ContentBase.cs
+++ b/src/Umbraco.Core/Models/ContentBase.cs
@@ -118,6 +118,16 @@ namespace Umbraco.Core.Models
}
}
+ ///
+ /// Sets the ParentId from the lazy integer id
+ ///
+ /// Id of the Parent
+ internal protected void SetLazyParentId(Lazy parentId)
+ {
+ _parentId = parentId;
+ OnPropertyChanged(ParentIdSelector);
+ }
+
///
/// Gets or sets the name of the entity
///
@@ -483,8 +493,6 @@ namespace Umbraco.Core.Models
get { return _lastInvalidProperties; }
}
- public abstract void ChangeTrashedState(bool isTrashed, int parentId = -20);
-
#region Dirty property handling
///
diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs
index e91996e32a..ceed239d53 100644
--- a/src/Umbraco.Core/Models/ContentExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentExtensions.cs
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web;
-using System.Xml;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -17,9 +14,6 @@ using Umbraco.Core.IO;
using Umbraco.Core.Media;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Strings;
-using Umbraco.Core.Persistence;
-using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models
@@ -43,175 +37,184 @@ namespace Umbraco.Core.Models
}
///
- /// Determines if the item should be persisted at all
+ /// Determines whether the content should be persisted.
///
- ///
- ///
- ///
- /// In one particular case, a content item shouldn't be persisted:
- /// * The item exists and is published
- /// * A call to ContentService.Save is made
- /// * The item has not been modified whatsoever apart from changing it's published status from published to saved
- ///
- /// In this case, there is no reason to make any database changes at all
- ///
+ /// The content.
+ /// True is the content should be persisted, otherwise false.
+ /// See remarks in overload.
internal static bool RequiresSaving(this IContent entity)
{
- var publishedState = ((Content)entity).PublishedState;
- return RequiresSaving(entity, publishedState);
+ return RequiresSaving(entity, ((Content) entity).PublishedState);
}
///
- /// Determines if the item should be persisted at all
+ /// Determines whether the content should be persisted.
///
- ///
- ///
- ///
+ /// The content.
+ /// The published state of the content.
+ /// True is the content should be persisted, otherwise false.
///
- /// In one particular case, a content item shouldn't be persisted:
- /// * The item exists and is published
- /// * A call to ContentService.Save is made
- /// * The item has not been modified whatsoever apart from changing it's published status from published to saved
- ///
- /// In this case, there is no reason to make any database changes at all
+ /// This is called by the repository when persisting an existing content, to
+ /// figure out whether it needs to persist the content at all.
///
internal static bool RequiresSaving(this IContent entity, PublishedState publishedState)
{
- var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished;
- //check if any user prop has changed
- var propertyValueChanged = entity.IsAnyUserPropertyDirty();
-
- //We need to know if any other property apart from Published was changed here
- //don't create a new version if the published state has changed to 'Save' but no data has actually been changed
- if (publishedChanged && entity.Published == false && propertyValueChanged == false)
+ // note: publishedState is always the entity's PublishedState except for tests
+
+ var content = (Content) entity;
+ var userPropertyChanged = content.IsAnyUserPropertyDirty();
+ var dirtyProps = content.GetDirtyProperties();
+ //var contentPropertyChanged = content.IsEntityDirty();
+ var contentPropertyChangedExceptPublished = dirtyProps.Any(x => x != "Published");
+
+ // we don't want to save (write to DB) if we are "saving" either a published content
+ // (.Saving) or an unpublished content (.Unpublished) and strictly nothing has changed
+
+ var noSave = (publishedState == PublishedState.Saving || publishedState == PublishedState.Unpublished)
+ && userPropertyChanged == false
+ && contentPropertyChangedExceptPublished == false;
+
+ return noSave == false;
+ }
+
+ ///
+ /// Determines whether a new version of the content should be created.
+ ///
+ /// The content.
+ /// True if a new version should be created, otherwise false.
+ /// See remarks in overload.
+ internal static bool RequiresNewVersion(this IContent entity)
+ {
+ return RequiresNewVersion(entity, ((Content) entity).PublishedState);
+ }
+
+ ///
+ /// Determines whether a new version of the content should be created.
+ ///
+ /// The content.
+ /// The published state of the content.
+ /// True if a new version should be created, otherwise false.
+ ///
+ /// This is called by the repository when persisting an existing content, to
+ /// figure out whether it needs to create a new version for that content.
+ /// A new version needs to be created when:
+ /// * The publish status is changed
+ /// * The language is changed
+ /// * A content property is changed (? why ?)
+ /// * The item is already published and is being published again and any property value is changed (to enable a rollback)
+ ///
+ internal static bool RequiresNewVersion(this IContent entity, PublishedState publishedState)
+ {
+ // note: publishedState is always the entity's PublishedState except for tests
+
+ // read
+ // http://issues.umbraco.org/issue/U4-2589 (save & publish & creating new versions)
+ // http://issues.umbraco.org/issue/U4-3404 (pressing preview does save then preview)
+ // http://issues.umbraco.org/issue/U4-5510 (previewing & creating new versions)
+ //
+ // slightly modifying the rules to make more sense (marked with CHANGE)
+ // but should respect the result of the discussions in those issues
+
+ // figure out whether .Language has changed
+ // this language stuff was an old POC and should be removed
+ var hasLanguageChanged = entity.IsPropertyDirty("Language");
+ if (hasLanguageChanged)
+ return true; // language change => new version
+
+ var content = (Content) entity;
+ //var contentPropertyChanged = content2.IsEntityDirty();
+ var userPropertyChanged = content.IsAnyUserPropertyDirty();
+ var dirtyProps = content.GetDirtyProperties();
+ var contentPropertyChangedExceptPublished = dirtyProps.Any(x => x != "Published");
+ var wasPublished = content.PublishedOriginal;
+
+ switch (publishedState)
{
- //at this point we need to check if any non property value has changed that wasn't the published state
- var changedProps = ((TracksChangesEntityBase)entity).GetDirtyProperties();
- if (changedProps.Any(x => x != "Published") == false)
- {
+ case PublishedState.Publishing:
+ // changed state, publishing either a published or an unpublished version:
+ // DO create a new (published) version IF it was published already AND
+ // anything has changed, else can reuse the current version
+ return (contentPropertyChangedExceptPublished || userPropertyChanged) && wasPublished;
+
+ case PublishedState.Unpublishing:
+ // changed state, unpublishing a published version:
+ // DO create a new (draft) version and preserve the (formerly) published
+ // version for rollback purposes IF the version that's being saved is the
+ // published version, else it's a draft that we can reuse
+ return wasPublished;
+
+ case PublishedState.Saving:
+ // changed state, saving a published version:
+ // DO create a new (draft) version and preserve the published version IF
+ // anything has changed, else do NOT create a new version (pointless)
+ return contentPropertyChangedExceptPublished || userPropertyChanged;
+
+ case PublishedState.Published:
+ // unchanged state, saving a published version:
+ // (can happen eg when moving content, never otherwise)
+ // do NOT create a new version as we're just saving after operations (eg
+ // move) that cannot be rolled back anyway - ensure that's really it
+ if (userPropertyChanged)
+ throw new InvalidOperationException("Invalid PublishedState \"Published\" with user property changes.");
return false;
- }
+
+ case PublishedState.Unpublished:
+ // unchanged state, saving an unpublished version:
+ // do NOT create a new version for user property changes,
+ // BUT create a new version in case of content property changes, for
+ // rollback purposes
+ return contentPropertyChangedExceptPublished;
+
+ default:
+ throw new NotSupportedException();
}
-
- return true;
}
///
- /// Determines if a new version should be created
+ /// Determines whether the database published flag should be cleared for versions
+ /// other than this content version.
///
- ///
- ///
+ /// The content.
+ /// True if the published flag should be cleared, otherwise false.
+ /// See remarks in overload.
+ internal static bool RequiresClearPublishedFlag(this IContent entity)
+ {
+ var publishedState = ((Content) entity).PublishedState;
+ var requiresNewVersion = entity.RequiresNewVersion(publishedState);
+ return entity.RequiresClearPublishedFlag(publishedState, requiresNewVersion);
+ }
+
+ ///
+ /// Determines whether the database published flag should be cleared for versions
+ /// other than this content version.
+ ///
+ /// The content.
+ /// The published state of the content.
+ /// Indicates whether the content is a new version.
+ /// True if the published flag should be cleared, otherwise false.
///
- /// A new version needs to be created when:
- /// * The publish status is changed
- /// * The language is changed
- /// * The item is already published and is being published again and any property value is changed (to enable a rollback)
+ /// This is called by the repository when persisting an existing content, to
+ /// figure out whether it needs to clear the published flag for other versions.
///
- internal static bool ShouldCreateNewVersion(this IContent entity)
+ internal static bool RequiresClearPublishedFlag(this IContent entity, PublishedState publishedState, bool isNewVersion)
{
- var publishedState = ((Content)entity).PublishedState;
- return ShouldCreateNewVersion(entity, publishedState);
- }
+ // note: publishedState is always the entity's PublishedState except for tests
- ///
- /// Returns a list of all dirty user defined properties
- ///
- ///
- public static IEnumerable GetDirtyUserProperties(this IContentBase entity)
- {
- return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias);
- }
-
- public static bool IsAnyUserPropertyDirty(this IContentBase entity)
- {
- return entity.Properties.Any(x => x.IsDirty());
- }
-
- public static bool WasAnyUserPropertyDirty(this IContentBase entity)
- {
- return entity.Properties.Any(x => x.WasDirty());
- }
-
- ///
- /// Determines if a new version should be created
- ///
- ///
- ///
- ///
- ///
- /// A new version needs to be created when:
- /// * The publish status is changed
- /// * The language is changed
- /// * The item is already published and is being published again and any property value is changed (to enable a rollback)
- ///
- internal static bool ShouldCreateNewVersion(this IContent entity, PublishedState publishedState)
- {
- //check if the published state has changed or the language
- var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished;
- var langChanged = entity.IsPropertyDirty("Language");
- var contentChanged = publishedChanged || langChanged;
-
- //check if any user prop has changed
- var propertyValueChanged = entity.IsAnyUserPropertyDirty();
-
- //return true if published or language has changed
- if (contentChanged)
- {
+ // new, published version => everything else must be cleared
+ if (isNewVersion && entity.Published)
return true;
- }
- //check if any content prop has changed
- var contentDataChanged = ((Content)entity).IsEntityDirty();
+ // if that entity was published then that entity has the flag and
+ // it does not need to be cleared for other versions
+ // NOT TRUE when unpublishing we create a NEW version
+ //var wasPublished = ((Content)entity).PublishedOriginal;
+ //if (wasPublished)
+ // return false;
- //return true if the item is published and a property has changed or if any content property has changed
- return (propertyValueChanged && publishedState == PublishedState.Published) || contentDataChanged;
- }
-
- ///
- /// Determines if the published db flag should be set to true for the current entity version and all other db
- /// versions should have their flag set to false.
- ///
- ///
- ///
- ///
- /// This is determined by:
- /// * If a new version is being created and the entity is published
- /// * If the published state has changed and the entity is published OR the entity has been un-published.
- ///
- internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity)
- {
- var publishedState = ((Content)entity).PublishedState;
- return entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, entity.ShouldCreateNewVersion(publishedState));
- }
-
- ///
- /// Determines if the published db flag should be set to true for the current entity version and all other db
- /// versions should have their flag set to false.
- ///
- ///
- ///
- ///
- ///
- ///
- /// This is determined by:
- /// * If a new version is being created and the entity is published
- /// * If the published state has changed and the entity is published OR the entity has been un-published.
- ///
- internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity, PublishedState publishedState, bool isCreatingNewVersion)
- {
- if (isCreatingNewVersion && entity.Published)
- {
- return true;
- }
-
- //If Published state has changed then previous versions should have their publish state reset.
- //If state has been changed to unpublished the previous versions publish state should also be reset.
- if (entity.IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished))
- {
- return true;
- }
-
- return false;
+ // clear whenever we are publishing or unpublishing
+ // publishing: because there might be a previously published version, which needs to be cleared
+ // unpublishing: same - we might be a saved version, not the published one, which needs to be cleared
+ return publishedState == PublishedState.Publishing || publishedState == PublishedState.Unpublishing;
}
///
@@ -312,9 +315,9 @@ namespace Umbraco.Core.Models
return content.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Contains(recycleBinId.ToInvariantString());
}
-
+
///
- /// Removes characters that are not valide XML characters from all entity properties
+ /// Removes characters that are not valide XML characters from all entity properties
/// of type string. See: http://stackoverflow.com/a/961504/5018
///
///
@@ -454,7 +457,7 @@ namespace Umbraco.Core.Models
/// The containing the file that will be uploaded
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value)
{
- // Ensure we get the filename without the path in IE in intranet mode
+ // Ensure we get the filename without the path in IE in intranet mode
// http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
var fileName = value.FileName;
if (fileName.LastIndexOf(@"\") > 0)
@@ -597,7 +600,7 @@ namespace Umbraco.Core.Models
#endregion
#region User/Profile methods
-
+
///
/// Gets the for the Creator of this media item.
///
@@ -672,7 +675,7 @@ namespace Umbraco.Core.Models
/////
/////
/////
- ///// The tags returned are only relavent for published content & saved media or members
+ ///// The tags returned are only relavent for published content & saved media or members
/////
//public static IEnumerable GetTags(this IContentBase content, string propertyTypeAlias, string tagGroup = "default")
//{
@@ -909,5 +912,24 @@ namespace Umbraco.Core.Models
return ((PackagingService)(packagingService)).Export(member);
}
#endregion
+
+ #region Dirty
+
+ public static IEnumerable GetDirtyUserProperties(this IContentBase entity)
+ {
+ return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias);
+ }
+
+ public static bool IsAnyUserPropertyDirty(this IContentBase entity)
+ {
+ return entity.Properties.Any(x => x.IsDirty());
+ }
+
+ public static bool WasAnyUserPropertyDirty(this IContentBase entity)
+ {
+ return entity.Properties.Any(x => x.WasDirty());
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs
index 0926e48e31..d711cf5a1a 100644
--- a/src/Umbraco.Core/Models/ContentType.cs
+++ b/src/Umbraco.Core/Models/ContentType.cs
@@ -152,31 +152,5 @@ namespace Umbraco.Core.Models
{
return DeepCloneWithResetIdentities(alias);
}
-
- ///
- /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
- ///
- ///
- public IContentType DeepCloneWithResetIdentities(string alias)
- {
- var clone = (ContentType)DeepClone();
- clone.Alias = alias;
- clone.Key = Guid.Empty;
- foreach (var propertyGroup in clone.PropertyGroups)
- {
- propertyGroup.ResetIdentity();
- propertyGroup.ResetDirtyProperties(false);
- }
- foreach (var propertyType in clone.PropertyTypes)
- {
- propertyType.ResetIdentity();
- propertyType.ResetDirtyProperties(false);
- }
-
- clone.ResetIdentity();
- clone.ResetDirtyProperties(false);
- return clone;
- }
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs
index a0305d2cfb..21ff40ce05 100644
--- a/src/Umbraco.Core/Models/ContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/ContentTypeBase.cs
@@ -642,5 +642,26 @@ namespace Umbraco.Core.Models
return clone;
}
+
+ public IContentType DeepCloneWithResetIdentities(string alias)
+ {
+ var clone = (ContentType)DeepClone();
+ clone.Alias = alias;
+ clone.Key = Guid.Empty;
+ foreach (var propertyGroup in clone.PropertyGroups)
+ {
+ propertyGroup.ResetIdentity();
+ propertyGroup.ResetDirtyProperties(false);
+ }
+ foreach (var propertyType in clone.PropertyTypes)
+ {
+ propertyType.ResetIdentity();
+ propertyType.ResetDirtyProperties(false);
+ }
+
+ clone.ResetIdentity();
+ clone.ResetDirtyProperties(false);
+ return clone;
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ContentTypeExtensions.cs b/src/Umbraco.Core/Models/ContentTypeExtensions.cs
index a47b430979..6301b46950 100644
--- a/src/Umbraco.Core/Models/ContentTypeExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentTypeExtensions.cs
@@ -14,8 +14,8 @@ namespace Umbraco.Core.Models
public static IEnumerable Descendants(this IContentTypeBase contentType)
{
var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
- var descendants = contentTypeService.GetContentTypeChildren(contentType.Id)
- .SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id));
+ var descendants = contentTypeService.GetChildren(contentType.Id)
+ .SelectRecursive(type => contentTypeService.GetChildren(type.Id));
return descendants;
}
diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs
index 7d2075cb65..d7e26808c8 100644
--- a/src/Umbraco.Core/Models/IContentBase.cs
+++ b/src/Umbraco.Core/Models/IContentBase.cs
@@ -72,12 +72,5 @@ namespace Umbraco.Core.Models
///
/// True if content is valid otherwise false
bool IsValid();
-
- ///
- /// Changes the Trashed state of the content object
- ///
- /// Boolean indicating whether content is trashed (true) or not trashed (false)
- ///
- void ChangeTrashedState(bool isTrashed, int parentId = -20);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs
index a7e794a400..d91fe9fdf8 100644
--- a/src/Umbraco.Core/Models/Media.cs
+++ b/src/Umbraco.Core/Models/Media.cs
@@ -112,7 +112,7 @@ namespace Umbraco.Core.Models
///
/// Boolean indicating whether content is trashed (true) or not trashed (false)
///
- public override void ChangeTrashedState(bool isTrashed, int parentId = -20)
+ public void ChangeTrashedState(bool isTrashed, int parentId = -20)
{
Trashed = isTrashed;
//The Media Recycle Bin Id is -21 so we correct that here
diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs
index 70c21e4307..ace6c2bf67 100644
--- a/src/Umbraco.Core/Models/Member.cs
+++ b/src/Umbraco.Core/Models/Member.cs
@@ -522,11 +522,6 @@ namespace Umbraco.Core.Models
get { return _contentType; }
}
- public override void ChangeTrashedState(bool isTrashed, int parentId = -20)
- {
- throw new NotSupportedException("Members can't be trashed as no Recycle Bin exists, so use of this method is invalid");
- }
-
/* Internal experiment - only used for mapping queries.
* Adding these to have first level properties instead of the Properties collection.
*/
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
index d05960b08f..9baf0c1024 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
@@ -166,10 +166,10 @@ namespace Umbraco.Core.Models.PublishedContent
switch (itemType)
{
case PublishedItemType.Content:
- contentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias);
+ contentType = ApplicationContext.Current.Services.ContentTypeService.Get(alias);
break;
case PublishedItemType.Media:
- contentType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias);
+ contentType = ApplicationContext.Current.Services.MediaTypeService.Get(alias);
break;
case PublishedItemType.Member:
contentType = ApplicationContext.Current.Services.MemberTypeService.Get(alias);
diff --git a/src/Umbraco.Core/Models/PublishedState.cs b/src/Umbraco.Core/Models/PublishedState.cs
index 4469eba9cc..d169262802 100644
--- a/src/Umbraco.Core/Models/PublishedState.cs
+++ b/src/Umbraco.Core/Models/PublishedState.cs
@@ -1,9 +1,54 @@
-namespace Umbraco.Core.Models
+using System;
+
+namespace Umbraco.Core.Models
{
+ ///
+ /// The IContent states of a content version.
+ ///
public enum PublishedState
{
+ // when a content version is loaded, its state is one of those two:
+
+ ///
+ /// The version is published.
+ ///
Published,
+
+ ///
+ /// The version is not published.
+ ///
+ /// Also: the version is being saved, in order to register changes
+ /// made to an unpublished version of the content.
Unpublished,
- Saved
+
+ // legacy - remove
+ [Obsolete("kill!", true)]
+ Saved,
+
+ // when it is saved, its state can also be one of those:
+
+ ///
+ /// The version is being saved, in order to register changes made to a published content.
+ ///
+ /// The Saving state is transitional. Once the version
+ /// is saved, its state changes to Unpublished.
+ Saving,
+
+ ///
+ /// The version is being saved, in order to publish the content.
+ ///
+ /// The Publishing state is transitional. Once the version
+ /// is saved, its state changes to Published. The content is published,
+ /// and all other versions are unpublished.
+ Publishing,
+
+ ///
+ /// The version is being saved, in order to unpublish the content.
+ ///
+ /// The Unpublishing state is transitional. Once the version
+ /// is saved, its state changes to Unpublished. The content and all
+ /// other versions are unpublished.
+ Unpublishing
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Rdbms/LockDto.cs b/src/Umbraco.Core/Models/Rdbms/LockDto.cs
new file mode 100644
index 0000000000..5227c15c66
--- /dev/null
+++ b/src/Umbraco.Core/Models/Rdbms/LockDto.cs
@@ -0,0 +1,24 @@
+using NPoco;
+using Umbraco.Core.Persistence.DatabaseAnnotations;
+
+namespace Umbraco.Core.Models.Rdbms
+{
+ [TableName("umbracoLock")]
+ [PrimaryKey("id")]
+ [ExplicitColumns]
+ internal class LockDto
+ {
+ [Column("id")]
+ [PrimaryKeyColumn(Name = "PK_umbracoLock")]
+ public int Id { get; set; }
+
+ [Column("value")]
+ [NullSetting(NullSetting = NullSettings.NotNull)]
+ public int Value { get; set; } = 1;
+
+ [Column("name")]
+ [NullSetting(NullSetting = NullSettings.NotNull)]
+ [Length(64)]
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs
new file mode 100644
index 0000000000..89d7cf7391
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs
@@ -0,0 +1,18 @@
+// ReSharper disable once CheckNamespace
+namespace Umbraco.Core
+{
+ static partial class Constants
+ {
+ public static class Locks
+ {
+ public const int Servers = -331;
+ public const int ContentTypes = -332;
+ public const int ContentTree = -333;
+ public const int MediaTree = -334;
+ public const int MemberTree = -335;
+ public const int MediaTypes = -336;
+ public const int MemberTypes = -337;
+ public const int Domains = -338;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs
index d21aea33e6..49445ffc9d 100644
--- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs
@@ -48,7 +48,6 @@ namespace Umbraco.Core.Persistence.Factories
ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null,
ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null,
Version = dto.ContentVersionDto.VersionId,
- PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished,
PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId
};
//on initial construction we don't want to have dirty properties tracked
diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs
index 66d9c8be0c..76f29f3350 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs
@@ -27,14 +27,19 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
/// Name of the table to create base data for
public void InitializeBaseData(string tableName)
{
- _logger.Info(string.Format("Creating data in table {0}", tableName));
+ _logger.Info($"Creating data in table {tableName}");
if(tableName.Equals("umbracoNode"))
{
- CreateUmbracNodeData();
+ CreateUmbracoNodeData();
}
- if(tableName.Equals("cmsContentType"))
+ if (tableName.Equals("umbracoLock"))
+ {
+ CreateUmbracoLockData();
+ }
+
+ if (tableName.Equals("cmsContentType"))
{
CreateCmsContentTypeData();
}
@@ -102,7 +107,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
_logger.Info(string.Format("Done creating data in table {0}", tableName));
}
- private void CreateUmbracNodeData()
+ private void CreateUmbracoNodeData()
{
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now });
@@ -135,15 +140,28 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now });
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1045, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1045", SortOrder = 2, UniqueId = new Guid("7E3962CC-CE20-4FFC-B661-5897A894BA7E"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
-
+
//TODO: We're not creating these for 7.0
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1039, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1039", SortOrder = 2, UniqueId = new Guid("06f349a9-c949-4b6a-8660-59c10451af42"), Text = "Ultimate Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
-
+
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
}
+ private void CreateUmbracoLockData()
+ {
+ // all lock objects
+ _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" });
+ _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.ContentTypes, Name = "ContentTypes" });
+ _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.ContentTree, Name = "ContentTree" });
+ _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.MediaTypes, Name = "MediaTypes" });
+ _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.MediaTree, Name = "MediaTree" });
+ _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" });
+ _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" });
+ _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" });
+ }
+
private void CreateCmsContentTypeData()
{
_database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true });
diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs
index 9891476b40..31e3152f04 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs
@@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{44, typeof (ExternalLoginDto)},
{45, typeof (MigrationDto)},
{46, typeof (UmbracoDeployChecksumDto)},
- {47, typeof (UmbracoDeployDependencyDto)}
+ {47, typeof (UmbracoDeployDependencyDto)},
+ {48, typeof (LockDto) }
};
#endregion
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockObjects.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockObjects.cs
new file mode 100644
index 0000000000..238196cc40
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockObjects.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Linq;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models.Rdbms;
+
+namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight
+{
+ [Migration("8.0.0", 101, GlobalSettings.UmbracoMigrationName)]
+ public class AddLockObjects : MigrationBase
+ {
+ public AddLockObjects(ILogger logger)
+ : base(logger)
+ { }
+
+ public override void Up()
+ {
+ // some may already exist, just ensure everything we need is here
+ EnsureLockObject(Constants.Locks.Servers, "Servers");
+ EnsureLockObject(Constants.Locks.ContentTypes, "ContentTypes");
+ EnsureLockObject(Constants.Locks.ContentTree, "ContentTree");
+ EnsureLockObject(Constants.Locks.MediaTree, "MediaTree");
+ EnsureLockObject(Constants.Locks.MemberTree, "MemberTree");
+ EnsureLockObject(Constants.Locks.MediaTypes, "MediaTypes");
+ EnsureLockObject(Constants.Locks.MemberTypes, "MemberTypes");
+ EnsureLockObject(Constants.Locks.Domains, "Domains");
+ }
+
+ public override void Down()
+ {
+ // not implemented
+ }
+
+ private void EnsureLockObject(int id, string name)
+ {
+ Execute.Code(db =>
+ {
+ var exists = db.Exists(id);
+ if (exists) return string.Empty;
+ // be safe: delete old umbracoNode lock objects if any
+ db.Execute($"DELETE FROM umbracoNode WHERE id={id};");
+ // then create umbracoLock object
+ db.Execute($"INSERT umbracoLock (id, name, value) VALUES ({id}, '{name}', 1);");
+ return string.Empty;
+ });
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockTable.cs
new file mode 100644
index 0000000000..ceff5e3537
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/AddLockTable.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Logging;
+
+namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionEight
+{
+ [Migration("8.0.0", 100, GlobalSettings.UmbracoMigrationName)]
+ public class AddLockTable : MigrationBase
+ {
+ public AddLockTable(ILogger logger)
+ : base(logger)
+ { }
+
+ public override void Up()
+ {
+ var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
+ if (tables.InvariantContains("umbracoLock") == false)
+ {
+ Create.Table("umbracoLock")
+ .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock")
+ .WithColumn("value").AsInt32().NotNullable()
+ .WithColumn("name").AsString(64).NotNullable();
+ }
+ }
+
+ public override void Down()
+ {
+ // not implemented
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs
index d605151249..c73201850c 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddServerRegistrationColumnsAndLock.cs
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe
Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0);
}
- EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
+ EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
}
public override void Down()
@@ -50,7 +50,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe
sortOrder = 0,
uniqueId = new Guid(uniqueId),
text = text,
- nodeObjectType = new Guid(Constants.ObjectTypes.LockObject),
+ nodeObjectType = Constants.ObjectTypes.LockObjectGuid,
createDate = DateTime.Now
});
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
index b3808ef804..7d4579611e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
@@ -1,17 +1,9 @@
using System;
using System.Collections.Generic;
-using System.Data;
using System.Globalization;
using System.Linq;
-using System.Linq.Expressions;
-using System.Net.Http.Headers;
-using System.Text;
using System.Xml.Linq;
using NPoco;
-using StackExchange.Profiling.Helpers.Dapper;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Dynamics;
-using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
@@ -30,7 +22,7 @@ using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
///
- /// Represents a repository for doing CRUD operations for
+ /// Represents a repository for doing CRUD operations for .
///
internal class ContentRepository : RecycleBinRepository, IContentRepository
{
@@ -44,9 +36,9 @@ namespace Umbraco.Core.Persistence.Repositories
public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection, IMappingResolver mappingResolver)
: base(work, cacheHelper, logger, contentSection, mappingResolver)
{
- if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository");
- if (templateRepository == null) throw new ArgumentNullException("templateRepository");
- if (tagRepository == null) throw new ArgumentNullException("tagRepository");
+ if (contentTypeRepository == null) throw new ArgumentNullException(nameof(contentTypeRepository));
+ if (templateRepository == null) throw new ArgumentNullException(nameof(templateRepository));
+ if (tagRepository == null) throw new ArgumentNullException(nameof(tagRepository));
_contentTypeRepository = contentTypeRepository;
_templateRepository = templateRepository;
_tagRepository = tagRepository;
@@ -73,7 +65,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (dto == null)
return null;
- var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId, sql);
+ var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId);
return content;
}
@@ -83,7 +75,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false);
if (ids.Any())
{
- sql.Where("umbracoNode.id in (@ids)", new { ids = ids });
+ sql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids });
}
//we only want the newest ones with this method
@@ -98,7 +90,8 @@ namespace Umbraco.Core.Persistence.Repositories
var translator = new SqlTranslator(sqlClause, query);
var sql = translator.Translate()
.Where(x => x.Newest)
- .OrderByDescending(x => x.VersionDate)
+ //.OrderByDescending(x => x.VersionDate)
+ .OrderBy(x => x.Level)
.OrderBy(x => x.SortOrder);
return MapQueryDtos(Database.Fetch(sql));
@@ -173,100 +166,12 @@ namespace Umbraco.Core.Persistence.Repositories
return list;
}
- protected override Guid NodeObjectTypeId
- {
- get { return new Guid(Constants.ObjectTypes.Document); }
- }
+ protected override Guid NodeObjectTypeId => new Guid(Constants.ObjectTypes.Document);
#endregion
#region Overrides of VersionableRepositoryBase
- public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null)
- {
-
- //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too.
- using (var tr = Database.GetTransaction())
- {
- //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted
- if (contentTypeIds == null)
- {
- var subQuery = Sql()
- .Select("DISTINCT cmsContentXml.nodeId")
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId);
-
- var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
- Database.Execute(deleteSql);
- }
- else
- {
- foreach (var id in contentTypeIds)
- {
- var id1 = id;
- var subQuery = Sql()
- .Select("cmsDocument.nodeId")
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .Where(dto => dto.Published)
- .Where( dto => dto.ContentTypeId == id1);
-
- var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
- Database.Execute(deleteSql);
- }
- }
-
- //now insert the data, again if something fails here, the whole transaction is reversed
- if (contentTypeIds == null)
- {
- var query = Query.Where(x => x.Published == true);
- RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
- }
- else
- {
- foreach (var contentTypeId in contentTypeIds)
- {
- //copy local
- var id = contentTypeId;
- var query = Query.Where(x => x.Published == true && x.ContentTypeId == id && x.Trashed == false);
- RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
- }
- }
-
- tr.Complete();
- }
- }
-
- private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize)
- {
- var pageIndex = 0;
- var total = long.MinValue;
- var processed = 0;
- do
- {
- //NOTE: This is an important call, we cannot simply make a call to:
- // GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending);
- // because that method is used to query 'latest' content items where in this case we don't necessarily
- // want latest content items because a pulished content item might not actually be the latest.
- // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982
- var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total,
- MapQueryDtos, "Path", Direction.Ascending, true);
-
- var xmlItems = (from descendant in descendants
- let xml = serializer(descendant)
- select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray();
-
- //bulk insert it into the database
- Database.BulkInsertRecords(SqlSyntax, xmlItems, tr);
-
- processed += xmlItems.Length;
-
- pageIndex++;
- } while (processed < total);
- }
-
public override IContent GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
@@ -278,7 +183,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (dto == null)
return null;
- var content = CreateContentFromDto(dto, versionId, sql);
+ var content = CreateContentFromDto(dto, versionId);
return content;
}
@@ -360,9 +265,7 @@ namespace Umbraco.Core.Persistence.Repositories
//ensure the default template is assigned
if (entity.Template == null)
- {
entity.Template = entity.ContentType.DefaultTemplate;
- }
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
@@ -375,10 +278,10 @@ namespace Umbraco.Core.Persistence.Repositories
//NOTE Should the logic below have some kind of fallback for empty parent ids ?
//Logic for setting Path, Level and SortOrder
- var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId });
+ var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId });
var level = parent.Level + 1;
var maxSortOrder = Database.ExecuteScalar(
- "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType",
+ "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentId = @ParentId AND nodeObjectType = @NodeObjectType",
new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId });
var sortOrder = maxSortOrder + 1;
@@ -387,7 +290,7 @@ namespace Umbraco.Core.Persistence.Repositories
nodeDto.Path = parent.Path;
nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture));
nodeDto.SortOrder = sortOrder;
- var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto);
+ var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto);
//Update with new correct path
nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId);
@@ -401,6 +304,8 @@ namespace Umbraco.Core.Persistence.Repositories
//Assign the same permissions to it as the parent node
// http://issues.umbraco.org/issue/U4-2161
+ // fixme STOP new-ing repos everywhere!
+ // var prepo = UnitOfWork.CreateRepository>();
var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper);
var parentPermissions = permissionsRepo.GetPermissionsForEntity(entity.ParentId).ToArray();
//if there are parent permissions then assign them, otherwise leave null and permissions will become the
@@ -448,15 +353,11 @@ namespace Umbraco.Core.Persistence.Repositories
//Update Properties with its newly set Id
foreach (var property in entity.Properties)
- {
property.Id = keyDictionary[property.PropertyTypeId];
- }
//lastly, check if we are a creating a published version , then update the tags table
if (entity.Published)
- {
- UpdatePropertyTags(entity, _tagRepository);
- }
+ UpdateEntityTags(entity, _tagRepository);
// published => update published version infos, else leave it blank
if (entity.Published)
@@ -476,7 +377,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected override void PersistUpdatedItem(IContent entity)
{
- var publishedState = ((Content)entity).PublishedState;
+ var content = (Content) entity;
+ var publishedState = content.PublishedState;
+ var publishedStateChanged = publishedState == PublishedState.Publishing || publishedState == PublishedState.Unpublishing;
//check if we need to make any database changes at all
if (entity.RequiresSaving(publishedState) == false)
@@ -486,11 +389,11 @@ namespace Umbraco.Core.Persistence.Repositories
}
//check if we need to create a new version
- bool shouldCreateNewVersion = entity.ShouldCreateNewVersion(publishedState);
- if (shouldCreateNewVersion)
+ var requiresNewVersion = entity.RequiresNewVersion(publishedState);
+ if (requiresNewVersion)
{
//Updates Modified date and Version Guid
- ((Content)entity).UpdatingEntity();
+ content.UpdatingEntity();
}
else
{
@@ -506,14 +409,10 @@ namespace Umbraco.Core.Persistence.Repositories
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
if (entity.IsPropertyDirty("ParentId"))
{
- var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId });
+ var parent = Database.First("WHERE id = @ParentId", new { /*ParentId =*/ entity.ParentId });
entity.Path = string.Concat(parent.Path, ",", entity.Id);
entity.Level = parent.Level + 1;
- var maxSortOrder =
- Database.ExecuteScalar(
- "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType",
- new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId });
- entity.SortOrder = maxSortOrder + 1;
+ entity.SortOrder = NextChildSortOrder(entity.ParentId);
//Question: If we move a node, should we update permissions to inherit from the new parent if the parent has permissions assigned?
// if we do that, then we'd need to propogate permissions all the way downward which might not be ideal for many people.
@@ -522,7 +421,7 @@ namespace Umbraco.Core.Persistence.Repositories
var factory = new ContentFactory(NodeObjectTypeId, entity.Id);
//Look up Content entry to get Primary for updating the DTO
- var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id });
+ var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { /*Id =*/ entity.Id });
factory.SetPrimaryKey(contentDto.PrimaryKey);
var dto = factory.BuildDto(entity);
@@ -538,37 +437,17 @@ namespace Umbraco.Core.Persistence.Repositories
Database.Update(newContentDto);
}
- //a flag that we'll use later to create the tags in the tag db table
- var publishedStateChanged = false;
-
//If Published state has changed then previous versions should have their publish state reset.
//If state has been changed to unpublished the previous versions publish state should also be reset.
//if (((ICanBeDirty)entity).IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished))
- if (entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, shouldCreateNewVersion))
- {
- var publishedDocs = Database.Fetch("WHERE nodeId = @Id AND published = @IsPublished", new { Id = entity.Id, IsPublished = true });
- foreach (var doc in publishedDocs)
- {
- var docDto = doc;
- docDto.Published = false;
- Database.Update(docDto);
- }
-
- //this is a newly published version so we'll update the tags table too (end of this method)
- publishedStateChanged = true;
- }
+ if (entity.RequiresClearPublishedFlag(publishedState, requiresNewVersion))
+ ClearPublishedFlag(entity);
//Look up (newest) entries by id in cmsDocument table to set newest = false
- var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true });
- foreach (var documentDto in documentDtos)
- {
- var docDto = documentDto;
- docDto.Newest = false;
- Database.Update(docDto);
- }
+ ClearNewestFlag(entity);
var contentVersionDto = dto.ContentVersionDto;
- if (shouldCreateNewVersion)
+ if (requiresNewVersion)
{
//Create a new version - cmsContentVersion
//Assumes a new Version guid and Version date (modified date) has been set
@@ -580,7 +459,7 @@ namespace Umbraco.Core.Persistence.Repositories
else
{
//In order to update the ContentVersion we need to retrieve its primary key id
- var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { Version = entity.Version });
+ var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version });
contentVersionDto.Id = contentVerDto.Id;
Database.Update(contentVersionDto);
@@ -595,7 +474,7 @@ namespace Umbraco.Core.Persistence.Repositories
//Add Properties
foreach (var propertyDataDto in propertyDataDtos)
{
- if (shouldCreateNewVersion == false && propertyDataDto.Id > 0)
+ if (requiresNewVersion == false && propertyDataDto.Id > 0)
{
Database.Update(propertyDataDto);
}
@@ -617,19 +496,38 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
- //lastly, check if we are a newly published version and then update the tags table
- if (publishedStateChanged && entity.Published)
+ // tags:
+ if (HasTagProperty(entity))
{
- UpdatePropertyTags(entity, _tagRepository);
- }
- else if (publishedStateChanged && (entity.Trashed || entity.Published == false))
- {
- //it's in the trash or not published remove all entity tags
- ClearEntityTags(entity, _tagRepository);
+ // if path-published, update tags, else clear tags
+ switch (content.PublishedState)
+ {
+ case PublishedState.Publishing:
+ // explicitely publishing, must update tags
+ UpdateEntityTags(entity, _tagRepository);
+ break;
+ case PublishedState.Unpublishing:
+ // explicitely unpublishing, must clear tags
+ ClearEntityTags(entity, _tagRepository);
+ break;
+ case PublishedState.Saving:
+ // saving, nothing to do
+ break;
+ case PublishedState.Published:
+ case PublishedState.Unpublished:
+ // no change, depends on path-published
+ // that should take care of trashing and un-trashing
+ if (IsPathPublished(entity)) // slightly expensive ;-(
+ UpdateEntityTags(entity, _tagRepository);
+ else
+ ClearEntityTags(entity, _tagRepository);
+ break;
+ }
}
// published => update published version infos,
// else if unpublished then clear published version infos
+ // else leave unchanged
if (entity.Published)
{
dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto
@@ -639,7 +537,7 @@ namespace Umbraco.Core.Persistence.Repositories
NodeId = dto.NodeId,
Published = true
};
- ((Content)entity).PublishedVersionGuid = dto.VersionId;
+ content.PublishedVersionGuid = dto.VersionId;
}
else if (publishedStateChanged)
{
@@ -650,12 +548,20 @@ namespace Umbraco.Core.Persistence.Repositories
NodeId = dto.NodeId,
Published = false
};
- ((Content)entity).PublishedVersionGuid = default(Guid);
+ content.PublishedVersionGuid = default(Guid);
}
entity.ResetDirtyProperties();
}
+ private int NextChildSortOrder(int parentId)
+ {
+ var maxSortOrder =
+ Database.ExecuteScalar(
+ "SELECT coalesce(max(sortOrder),0) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType",
+ new { ParentId = parentId, NodeObjectType = NodeObjectTypeId });
+ return maxSortOrder + 1;
+ }
#endregion
@@ -664,9 +570,7 @@ namespace Umbraco.Core.Persistence.Repositories
public IEnumerable GetByPublishedVersion(IQuery query)
{
// we WANT to return contents in top-down order, ie parents should come before children
- // ideal would be pure xml "document order" which can be achieved with:
- // ORDER BY substring(path, 1, len(path) - charindex(',', reverse(path))), sortOrder
- // but that's probably an overkill - sorting by level,sortOrder should be enough
+ // ideal would be pure xml "document order" - which we cannot achieve at database level
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator(sqlClause, query);
@@ -680,27 +584,42 @@ namespace Umbraco.Core.Persistence.Repositories
foreach (var dto in dtos)
{
- //Check in the cache first. If it exists there AND it is published
- // then we can use that entity. Otherwise if it is not published (which can be the case
- // because we only store the 'latest' entries in the cache which might not be the published
- // version)
+ // check cache first, if it exists and is published, use it
+ // it may exist and not be published as the cache has 'latest version used'
var fromCache = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId));
- //var fromCache = TryGetFromCache(dto.NodeId);
- if (fromCache != null && fromCache.Published)
- {
- yield return fromCache;
- }
- else
- {
- yield return CreateContentFromDto(dto, dto.VersionId, sql);
- }
+ yield return fromCache != null && fromCache.Published
+ ? fromCache
+ : CreateContentFromDto(dto, dto.VersionId);
}
}
- public int CountPublished()
+ public int CountPublished(string contentTypeAlias = null)
{
- var sql = GetBaseQuery(true).Where(x => x.Trashed == false)
- .Where(x => x.Published == true);
+ var sql = Sql();
+ if (contentTypeAlias.IsNullOrWhiteSpace())
+ {
+ sql.SelectCount()
+ .From()
+ .InnerJoin()
+ .On(left => left.NodeId, right => right.NodeId)
+ .Where(x => x.NodeObjectType == NodeObjectTypeId && x.Trashed == false)
+ .Where(x => x.Published);
+ }
+ else
+ {
+ sql.SelectCount()
+ .From()
+ .InnerJoin()
+ .On(left => left.NodeId, right => right.NodeId)
+ .InnerJoin()
+ .On(left => left.NodeId, right => right.NodeId)
+ .InnerJoin()
+ .On(left => left.NodeId, right => right.ContentTypeId)
+ .Where(x => x.NodeObjectType == NodeObjectTypeId && x.Trashed == false)
+ .Where(x => x.Alias == contentTypeAlias)
+ .Where(x => x.Published);
+ }
+
return Database.ExecuteScalar(sql);
}
@@ -710,10 +629,10 @@ namespace Umbraco.Core.Persistence.Repositories
repo.ReplaceEntityPermissions(permissionSet);
}
- public void ClearPublished(IContent content)
+ public void ClearPublishedFlag(IContent content)
{
- // race cond!
- var documentDtos = Database.Fetch("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true });
+ // no race cond if locked
+ var documentDtos = Database.Fetch("WHERE nodeId=@Id AND published=@IsPublished", new { /*Id =*/ content.Id, IsPublished = true });
foreach (var documentDto in documentDtos)
{
documentDto.Published = false;
@@ -721,6 +640,17 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
+ public void ClearNewestFlag(IContent content)
+ {
+ // no race cond if locked
+ var documentDtos = Database.Fetch("WHERE nodeId=@Id AND newest=@IsNewest", new { /*Id =*/ content.Id, IsNewest = true });
+ foreach (var documentDto in documentDtos)
+ {
+ documentDto.Newest = false;
+ Database.Update(documentDto);
+ }
+ }
+
///
/// Assigns a single permission to the current content item for the specified user ids
///
@@ -739,35 +669,6 @@ namespace Umbraco.Core.Persistence.Repositories
return repo.GetPermissionsForEntity(entityId);
}
- ///
- /// Adds/updates content/published xml
- ///
- ///
- ///
- public void AddOrUpdateContentXml(IContent content, Func xml)
- {
- _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml));
- }
-
- ///
- /// Used to remove the content xml for a content item
- ///
- ///
- public void DeleteContentXml(IContent content)
- {
- _contentXmlRepository.Delete(new ContentXmlEntity(content));
- }
-
- ///
- /// Adds/updates preview xml
- ///
- ///
- ///
- public void AddOrUpdatePreviewXml(IContent content, Func xml)
- {
- _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml));
- }
-
///
/// Gets paged content results
///
@@ -793,42 +694,40 @@ namespace Umbraco.Core.Persistence.Repositories
filterSql);
}
- ///
- /// Returns the persisted content's preview XML structure
- ///
- ///
- ///
- public XElement GetContentXml(int contentId)
+ public bool IsPathPublished(IContent content)
{
- var sql = Sql().SelectAll().From().Where(d => d.NodeId == contentId);
- var dto = Database.SingleOrDefault(sql);
- if (dto == null) return null;
- return XElement.Parse(dto.Xml);
- }
+ // fail fast
+ if (content.Path.StartsWith("-1,-20,"))
+ return false;
+ // succeed fast
+ if (content.ParentId == -1)
+ return content.HasPublishedVersion;
- ///
- /// Returns the persisted content's preview XML structure
- ///
- ///
- ///
- ///
- public XElement GetContentPreviewXml(int contentId, Guid version)
- {
- var sql = Sql().SelectAll().From()
- .Where(d => d.NodeId == contentId && d.VersionId == version);
- var dto = Database.SingleOrDefault(sql);
- if (dto == null) return null;
- return XElement.Parse(dto.Xml);
+ var syntaxUmbracoNode = SqlSyntax.GetQuotedTableName("umbracoNode");
+ var syntaxPath = SqlSyntax.GetQuotedColumnName("path");
+ var syntaxConcat = SqlSyntax.GetConcat(syntaxUmbracoNode + "." + syntaxPath, "',%'");
+
+ var sql = string.Format(@"SELECT COUNT({0}.{1})
+FROM {0}
+JOIN {2} ON ({0}.{1}={2}.{3} AND {2}.{4}=@published)
+WHERE (@path LIKE {5})",
+ syntaxUmbracoNode,
+ SqlSyntax.GetQuotedColumnName("id"),
+ SqlSyntax.GetQuotedTableName("cmsDocument"),
+ SqlSyntax.GetQuotedColumnName("nodeId"),
+ SqlSyntax.GetQuotedColumnName("published"),
+ syntaxConcat);
+
+ var count = Database.ExecuteScalar(sql, new { @published=true, @path=content.Path });
+ count += 1; // because content does not count
+ return count == content.Level;
}
#endregion
#region IRecycleBinRepository members
- protected override int RecycleBinId
- {
- get { return Constants.System.RecycleBinContent; }
- }
+ protected override int RecycleBinId => Constants.System.RecycleBinContent;
#endregion
@@ -885,7 +784,7 @@ namespace Umbraco.Core.Persistence.Repositories
return dtosWithContentTypes.Select(d => CreateContentFromDto(
d.dto,
contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId),
- templates.FirstOrDefault(tem => tem.Id == (d.dto.TemplateId.HasValue ? d.dto.TemplateId.Value : -1)),
+ templates.FirstOrDefault(tem => tem.Id == (d.dto.TemplateId ?? -1)),
propertyData[d.dto.NodeId]));
}
@@ -900,7 +799,7 @@ namespace Umbraco.Core.Persistence.Repositories
private IContent CreateContentFromDto(DocumentDto dto,
IContentType contentType,
ITemplate template,
- Models.PropertyCollection propCollection)
+ PropertyCollection propCollection)
{
var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
var content = factory.BuildEntity(dto);
@@ -929,9 +828,8 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
///
- ///
///
- private IContent CreateContentFromDto(DocumentDto dto, Guid versionId, Sql docSql)
+ private IContent CreateContentFromDto(DocumentDto dto, Guid versionId)
{
var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
@@ -979,7 +877,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant()))
{
- currentName = nodeName + string.Format(" ({0})", uniqueNumber);
+ currentName = nodeName + $" ({uniqueNumber})";
uniqueNumber++;
}
}
@@ -987,5 +885,151 @@ namespace Umbraco.Core.Persistence.Repositories
return currentName;
}
+
+ #region Xml - Should Move!
+
+ public void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null)
+ {
+
+ //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too.
+ using (var tr = Database.GetTransaction())
+ {
+ //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted
+ if (contentTypeIds == null)
+ {
+ var subQuery = Sql()
+ .Select("DISTINCT cmsContentXml.nodeId")
+ .From()
+ .InnerJoin()
+ .On(left => left.NodeId, right => right.NodeId);
+
+ var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
+ Database.Execute(deleteSql);
+ }
+ else
+ {
+ foreach (var id in contentTypeIds)
+ {
+ var id1 = id;
+ var subQuery = Sql()
+ .Select("cmsDocument.nodeId")
+ .From()
+ .InnerJoin()
+ .On(left => left.NodeId, right => right.NodeId)
+ .Where(dto => dto.Published)
+ .Where(dto => dto.ContentTypeId == id1);
+
+ var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
+ Database.Execute(deleteSql);
+ }
+ }
+
+ //now insert the data, again if something fails here, the whole transaction is reversed
+ if (contentTypeIds == null)
+ {
+ var query = Query.Where(x => x.Published);
+ RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
+ }
+ else
+ {
+ foreach (var contentTypeId in contentTypeIds)
+ {
+ //copy local
+ var id = contentTypeId;
+ var query = Query.Where(x => x.Published && x.ContentTypeId == id && x.Trashed == false);
+ RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
+ }
+ }
+
+ tr.Complete();
+ }
+ }
+
+ private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize)
+ {
+ var pageIndex = 0;
+ long total;
+ var processed = 0;
+ do
+ {
+ //NOTE: This is an important call, we cannot simply make a call to:
+ // GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending);
+ // because that method is used to query 'latest' content items where in this case we don't necessarily
+ // want latest content items because a pulished content item might not actually be the latest.
+ // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982
+ var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total,
+ MapQueryDtos, "Path", Direction.Ascending, true);
+
+ var xmlItems = (from descendant in descendants
+ let xml = serializer(descendant)
+ select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray();
+
+ //bulk insert it into the database
+ Database.BulkInsertRecords(SqlSyntax, xmlItems, tr);
+
+ processed += xmlItems.Length;
+
+ pageIndex++;
+ } while (processed < total);
+ }
+
+ ///
+ /// Adds/updates content/published xml
+ ///
+ ///
+ ///
+ public void AddOrUpdateContentXml(IContent content, Func xml)
+ {
+ _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml));
+ }
+
+ ///
+ /// Used to remove the content xml for a content item
+ ///
+ ///
+ public void DeleteContentXml(IContent content)
+ {
+ _contentXmlRepository.Delete(new ContentXmlEntity(content));
+ }
+
+ ///
+ /// Adds/updates preview xml
+ ///
+ ///
+ ///
+ public void AddOrUpdatePreviewXml(IContent content, Func xml)
+ {
+ _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml));
+ }
+
+ ///
+ /// Returns the persisted content's preview XML structure
+ ///
+ ///
+ ///
+ public XElement GetContentXml(int contentId)
+ {
+ var sql = Sql().SelectAll().From().Where(d => d.NodeId == contentId);
+ var dto = Database.SingleOrDefault(sql);
+ if (dto == null) return null;
+ return XElement.Parse(dto.Xml);
+ }
+
+ ///
+ /// Returns the persisted content's preview XML structure
+ ///
+ ///
+ ///
+ ///
+ public XElement GetContentPreviewXml(int contentId, Guid version)
+ {
+ var sql = Sql().SelectAll().From()
+ .Where(d => d.NodeId == contentId && d.VersionId == version);
+ var dto = Database.SingleOrDefault(sql);
+ if (dto == null) return null;
+ return XElement.Parse(dto.Xml);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs
index 40cf02af38..aacd51c685 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs
@@ -3,26 +3,21 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
-using Umbraco.Core.Events;
-using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
-
-using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
-using Umbraco.Core.Services;
namespace Umbraco.Core.Persistence.Repositories
{
///
/// Represents a repository for doing CRUD operations for
///
- internal class ContentTypeRepository : ContentTypeBaseRepository, IContentTypeRepository
+ internal class ContentTypeRepository : ContentTypeRepositoryBase, IContentTypeRepository
{
private readonly ITemplateRepository _templateRepository;
@@ -51,6 +46,23 @@ namespace Umbraco.Core.Persistence.Repositories
return GetAll().FirstOrDefault(x => x.Id == id);
}
+ protected override IContentType PerformGet(Guid id)
+ {
+ //use the underlying GetAll which will force cache all content types
+ return GetAll().FirstOrDefault(x => x.Key == id);
+ }
+
+ protected override IContentType PerformGet(string alias)
+ {
+ //use the underlying GetAll which will force cache all content types
+ return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias));
+ }
+
+ protected override bool PerformExists(Guid id)
+ {
+ return GetAll().FirstOrDefault(x => x.Key == id) != null;
+ }
+
protected override IEnumerable PerformGetAll(params int[] ids)
{
if (ids.Any())
@@ -63,6 +75,12 @@ namespace Umbraco.Core.Persistence.Repositories
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, this, _templateRepository);
}
+
+ protected override IEnumerable PerformGetAll(params Guid[] ids)
+ {
+ // use the underlying GetAll which will force cache all content types
+ return ids.Any() ? GetAll().Where(x => ids.Contains(x.Key)) : GetAll();
+ }
protected override IEnumerable PerformGetByQuery(IQuery query)
{
var sqlClause = GetBaseQuery(false);
@@ -154,28 +172,14 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IEnumerable GetDeleteClauses()
{
- var list = new List
- {
- "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id",
- "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id",
- "DELETE FROM cmsTagRelationship WHERE nodeId = @Id",
- "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id",
- "DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @Id",
- "DELETE FROM cmsContentType2ContentType WHERE parentContentTypeId = @Id",
- "DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @Id",
- "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id",
- "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id",
- "DELETE FROM cmsDocumentType WHERE contentTypeNodeId = @Id",
- "DELETE FROM cmsContentType WHERE nodeId = @Id",
- "DELETE FROM umbracoNode WHERE id = @Id"
- };
- return list;
+ var l = (List) base.GetDeleteClauses(); // we know it's a list
+ l.Add("DELETE FROM cmsDocumentType WHERE contentTypeNodeId = @Id");
+ l.Add("DELETE FROM cmsContentType WHERE nodeId = @Id");
+ l.Add("DELETE FROM umbracoNode WHERE id = @Id");
+ return l;
}
- protected override Guid NodeObjectTypeId
- {
- get { return new Guid(Constants.ObjectTypes.DocumentType); }
- }
+ protected override Guid NodeObjectTypeId => Constants.ObjectTypes.DocumentTypeGuid;
///
/// Deletes a content type
@@ -290,36 +294,5 @@ namespace Umbraco.Core.Persistence.Repositories
entity.ResetDirtyProperties();
}
-
- protected override IContentType PerformGet(Guid id)
- {
- //use the underlying GetAll which will force cache all content types
- return GetAll().FirstOrDefault(x => x.Key == id);
- }
-
- protected override IContentType PerformGet(string alias)
- {
- //use the underlying GetAll which will force cache all content types
- return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias));
- }
-
- protected override IEnumerable PerformGetAll(params Guid[] ids)
- {
- //use the underlying GetAll which will force cache all content types
-
- if (ids.Any())
- {
- return GetAll().Where(x => ids.Contains(x.Key));
- }
- else
- {
- return GetAll();
- }
- }
-
- protected override bool PerformExists(Guid id)
- {
- return GetAll().FirstOrDefault(x => x.Key == id) != null;
- }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs
similarity index 94%
rename from src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs
rename to src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs
index a446a0a390..cd1ef66168 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs
@@ -1,1230 +1,1274 @@
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Diagnostics;
-using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using NPoco;
-using Umbraco.Core.Events;
-using Umbraco.Core.Exceptions;
-using Umbraco.Core.Logging;
-using Umbraco.Core.Models;
-using Umbraco.Core.Models.EntityBase;
-using Umbraco.Core.Models.Rdbms;
-
-using Umbraco.Core.Persistence.Factories;
-using Umbraco.Core.Persistence.Mappers;
-using Umbraco.Core.Persistence.Querying;
-using Umbraco.Core.Persistence.SqlSyntax;
-using Umbraco.Core.Persistence.UnitOfWork;
-using Umbraco.Core.Services;
-
-namespace Umbraco.Core.Persistence.Repositories
-{
- ///
- /// Represent an abstract Repository for ContentType based repositories
- ///
- /// Exposes shared functionality
- ///
- internal abstract class ContentTypeBaseRepository : NPocoRepositoryBase, IReadRepository
- where TEntity : class, IContentTypeComposition
- {
- protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMappingResolver mappingResolver)
- : base(work, cache, logger, mappingResolver)
- {
- }
-
- public IEnumerable> Move(TEntity toMove, EntityContainer container)
- {
- var parentId = Constants.System.Root;
- if (container != null)
- {
- // Check on paths
- if ((string.Format(",{0},", container.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1)
- {
- throw new DataOperationException(MoveOperationStatusType.FailedNotAllowedByPath);
- }
- parentId = container.Id;
- }
-
- //used to track all the moved entities to be given to the event
- var moveInfo = new List>
- {
- new MoveEventInfo(toMove, toMove.Path, parentId)
- };
-
-
- // get the level delta (old pos to new pos)
- var levelDelta = container == null
- ? 1 - toMove.Level
- : container.Level + 1 - toMove.Level;
-
- // move to parent (or -1), update path, save
- toMove.ParentId = parentId;
- var toMovePath = toMove.Path + ","; // save before changing
- toMove.Path = (container == null ? Constants.System.Root.ToString() : container.Path) + "," + toMove.Id;
- toMove.Level = container == null ? 1 : container.Level + 1;
- AddOrUpdate(toMove);
-
- //update all descendants, update in order of level
- var descendants = GetByQuery(Query.Where(type => type.Path.StartsWith(toMovePath)));
- var paths = new Dictionary();
- paths[toMove.Id] = toMove.Path;
-
- foreach (var descendant in descendants.OrderBy(x => x.Level))
- {
- moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId));
-
- descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
- descendant.Level += levelDelta;
-
- AddOrUpdate(descendant);
- }
-
- return moveInfo;
- }
- ///
- /// Returns the content type ids that match the query
- ///
- ///
- ///
- protected IEnumerable PerformGetByQuery(IQuery query)
- {
- var sqlClause = Sql()
- .SelectAll()
- .From()
- .RightJoin()
- .On(left => left.Id, right => right.PropertyTypeGroupId)
- .InnerJoin()
- .On(left => left.DataTypeId, right => right.DataTypeId);
-
- var translator = new SqlTranslator(sqlClause, query);
- var sql = translator.Translate()
- .OrderBy(x => x.PropertyTypeGroupId);
-
- return Database
- .FetchOneToMany(x => x.PropertyTypeDtos, sql)
- .Select(x => x.ContentTypeNodeId).Distinct();
- }
-
- protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, DataTypeDatabaseType dbType, string propertyTypeAlias)
- {
- return new PropertyType(propertyEditorAlias, dbType, propertyTypeAlias);
- }
-
- protected void PersistNewBaseContentType(IContentTypeComposition entity)
- {
- var factory = new ContentTypeFactory();
- var dto = factory.BuildContentTypeDto(entity);
-
- //Cannot add a duplicate content type type
- var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType
-INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id
-WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias
-AND umbracoNode.nodeObjectType = @objectType",
- new { alias = entity.Alias, objectType = NodeObjectTypeId });
- if (exists > 0)
- {
- throw new DuplicateNameException("An item with the alias " + entity.Alias + " already exists");
- }
-
- //Logic for setting Path, Level and SortOrder
- var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId });
- int level = parent.Level + 1;
- int sortOrder =
- Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType",
- new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId });
-
- //Create the (base) node data - umbracoNode
- var nodeDto = dto.NodeDto;
- nodeDto.Path = parent.Path;
- nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture));
- nodeDto.SortOrder = sortOrder;
- var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto);
-
- //Update with new correct path
- nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId);
- Database.Update(nodeDto);
-
- //Update entity with correct values
- entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set
- entity.Path = nodeDto.Path;
- entity.SortOrder = sortOrder;
- entity.Level = level;
-
- //Insert new ContentType entry
- dto.NodeId = nodeDto.NodeId;
- Database.Insert(dto);
-
- //Insert ContentType composition in new table
- foreach (var composition in entity.ContentTypeComposition)
- {
- if (composition.Id == entity.Id) continue;//Just to ensure that we aren't creating a reference to ourself.
-
- if (composition.HasIdentity)
- {
- Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id });
- }
- else
- {
- //Fallback for ContentTypes with no identity
- var contentTypeDto = Database.FirstOrDefault("WHERE alias = @Alias", new { Alias = composition.Alias });
- if (contentTypeDto != null)
- {
- Database.Insert(new ContentType2ContentTypeDto { ParentId = contentTypeDto.NodeId, ChildId = entity.Id });
- }
- }
- }
-
- //Insert collection of allowed content types
- foreach (var allowedContentType in entity.AllowedContentTypes)
- {
- Database.Insert(new ContentTypeAllowedContentTypeDto
- {
- Id = entity.Id,
- AllowedId = allowedContentType.Id.Value,
- SortOrder = allowedContentType.SortOrder
- });
- }
-
- var propertyFactory = new PropertyGroupFactory(nodeDto.NodeId);
-
- //Insert Tabs
- foreach (var propertyGroup in entity.PropertyGroups)
- {
- var tabDto = propertyFactory.BuildGroupDto(propertyGroup);
- var primaryKey = Convert.ToInt32(Database.Insert(tabDto));
- propertyGroup.Id = primaryKey;//Set Id on PropertyGroup
-
- //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group
- //unless the PropertyGroupId has already been changed.
- foreach (var propertyType in propertyGroup.PropertyTypes)
- {
- if (propertyType.IsPropertyDirty("PropertyGroupId") == false)
- {
- var tempGroup = propertyGroup;
- propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id);
- }
- }
- }
-
- //Insert PropertyTypes
- foreach (var propertyType in entity.PropertyTypes)
- {
- var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int);
- //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias
- if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int))
- {
- AssignDataTypeFromPropertyEditor(propertyType);
- }
- var propertyTypeDto = propertyFactory.BuildPropertyTypeDto(tabId, propertyType);
- int typePrimaryKey = Convert.ToInt32(Database.Insert(propertyTypeDto));
- propertyType.Id = typePrimaryKey; //Set Id on new PropertyType
-
- //Update the current PropertyType with correct PropertyEditorAlias and DatabaseType
- var dataTypeDto = Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = propertyTypeDto.DataTypeId });
- propertyType.PropertyEditorAlias = dataTypeDto.PropertyEditorAlias;
- propertyType.DataTypeDatabaseType = dataTypeDto.DbType.EnumParse(true);
- }
- }
-
- protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
- {
- var factory = new ContentTypeFactory();
- var dto = factory.BuildContentTypeDto(entity);
-
- // ensure the alias is not used already
- var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsContentType
-INNER JOIN umbracoNode ON cmsContentType.nodeId = umbracoNode.id
-WHERE cmsContentType." + SqlSyntax.GetQuotedColumnName("alias") + @"= @alias
-AND umbracoNode.nodeObjectType = @objectType
-AND umbracoNode.id <> @id",
- new { id = dto.NodeId, alias = dto.Alias, objectType = NodeObjectTypeId });
- if (exists > 0)
- throw new DuplicateNameException("An item with the alias " + dto.Alias + " already exists");
-
- // handle (update) the node
- var nodeDto = dto.NodeDto;
- Database.Update(nodeDto);
-
- // fixme - why? we are UPDATING so we should ALREADY have a PK!
- //Look up ContentType entry to get PrimaryKey for updating the DTO
- var dtoPk = Database.First("WHERE nodeId = @Id", new { Id = entity.Id });
- dto.PrimaryKey = dtoPk.PrimaryKey;
- Database.Update(dto);
-
- // handle (delete then recreate) compositions
- Database.Delete("WHERE childContentTypeId = @Id", new { Id = entity.Id });
- foreach (var composition in entity.ContentTypeComposition)
- Database.Insert(new ContentType2ContentTypeDto { ParentId = composition.Id, ChildId = entity.Id });
-
- //Removing a ContentType from a composition (U4-1690)
- //1. Find content based on the current ContentType: entity.Id
- //2. Find all PropertyTypes on the ContentType that was removed - tracked id (key)
- //3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one
- var compositionBase = entity as ContentTypeCompositionBase;
- if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null &&
- compositionBase.RemovedContentTypeKeyTracker.Any())
- {
- //Find Content based on the current ContentType
- var sql = Sql()
- .SelectAll()
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document))
- .Where(x => x.ContentTypeId == entity.Id);
-
- var contentDtos = Database.Fetch(sql);
- //Loop through all tracked keys, which corresponds to the ContentTypes that has been removed from the composition
- foreach (var key in compositionBase.RemovedContentTypeKeyTracker)
- {
- //Find PropertyTypes for the removed ContentType
- var propertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = key });
- //Loop through the Content that is based on the current ContentType in order to remove the Properties that are
- //based on the PropertyTypes that belong to the removed ContentType.
- foreach (var contentDto in contentDtos)
- {
- foreach (var propertyType in propertyTypes)
- {
- var nodeId = contentDto.NodeId;
- var propertyTypeId = propertyType.Id;
- var propertySql = Sql()
- .Select("cmsPropertyData.id")
- .From()
- .InnerJoin()
- .On(left => left.PropertyTypeId, right => right.Id)
- .Where(x => x.NodeId == nodeId)
- .Where(x => x.Id == propertyTypeId);
-
- //Finally delete the properties that match our criteria for removing a ContentType from the composition
- Database.Delete(new Sql("WHERE id IN (" + propertySql.SQL + ")", propertySql.Arguments));
- }
- }
- }
- }
-
- //Delete the allowed content type entries before adding the updated collection
- Database.Delete("WHERE Id = @Id", new { Id = entity.Id });
- //Insert collection of allowed content types
- foreach (var allowedContentType in entity.AllowedContentTypes)
- {
- Database.Insert(new ContentTypeAllowedContentTypeDto
- {
- Id = entity.Id,
- AllowedId = allowedContentType.Id.Value,
- SortOrder = allowedContentType.SortOrder
- });
- }
-
-
- if (((ICanBeDirty)entity).IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty()))
- {
- //Delete PropertyTypes by excepting entries from db with entries from collections
- var dbPropertyTypes = Database.Fetch("WHERE contentTypeId = @Id", new { Id = entity.Id });
- var dbPropertyTypeAlias = dbPropertyTypes.Select(x => x.Id);
- var entityPropertyTypes = entity.PropertyTypes.Where(x => x.HasIdentity).Select(x => x.Id);
- var items = dbPropertyTypeAlias.Except(entityPropertyTypes);
- foreach (var item in items)
- {
- //Before a PropertyType can be deleted, all Properties based on that PropertyType should be deleted.
- Database.Delete("WHERE propertyTypeId = @Id", new { Id = item });
- Database.Delete("WHERE propertytypeid = @Id", new { Id = item });
- Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId",
- new { Id = entity.Id, PropertyTypeId = item });
- }
- }
-
- if (entity.IsPropertyDirty("PropertyGroups") || entity.PropertyGroups.Any(x => x.IsDirty()))
- {
- // todo
- // we used to try to propagate tabs renaming downstream, relying on ParentId, but
- // 1) ParentId makes no sense (if a tab can be inherited from multiple composition
- // types) so we would need to figure things out differently, visiting downstream
- // content types and looking for tabs with the same name...
- // 2) It was not deployable as changing a content type changes other content types
- // that was not deterministic, because it would depend on the order of the changes.
- // That last point could be fixed if (1) is fixed, but then it still is an issue with
- // deploy because changing a content type changes other content types that are not
- // dependencies but dependents, and then what?
- //
- // So... for the time being, all renaming propagation is disabled. We just don't do it.
-
- // (all gone)
-
- // delete tabs that do not exist anymore
- // get the tabs that are currently existing (in the db)
- // get the tabs that we want, now
- // and derive the tabs that we want to delete
- var existingPropertyGroups = Database.Fetch("WHERE contentTypeNodeId = @id", new { id = entity.Id })
- .Select(x => x.Id)
- .ToList();
- var newPropertyGroups = entity.PropertyGroups.Select(x => x.Id).ToList();
- var tabsToDelete = existingPropertyGroups
- .Except(newPropertyGroups)
- .ToArray();
-
- // move properties to generic properties, and delete the tabs
- if (tabsToDelete.Length > 0)
- {
- Database.Update("SET propertyTypeGroupId=NULL WHERE propertyTypeGroupId IN (@ids)", new { ids = tabsToDelete });
- Database.Delete("WHERE id IN (@ids)", new { ids = tabsToDelete });
- }
- }
- var propertyGroupFactory = new PropertyGroupFactory(entity.Id);
-
- //Run through all groups to insert or update entries
- foreach (var propertyGroup in entity.PropertyGroups)
- {
- var tabDto = propertyGroupFactory.BuildGroupDto(propertyGroup);
- int groupPrimaryKey = propertyGroup.HasIdentity
- ? Database.Update(tabDto)
- : Convert.ToInt32(Database.Insert(tabDto));
- if (propertyGroup.HasIdentity == false)
- propertyGroup.Id = groupPrimaryKey; //Set Id on new PropertyGroup
-
- //Ensure that the PropertyGroup's Id is set on the PropertyTypes within a group
- //unless the PropertyGroupId has already been changed.
- foreach (var propertyType in propertyGroup.PropertyTypes)
- {
- if (propertyType.IsPropertyDirty("PropertyGroupId") == false)
- {
- var tempGroup = propertyGroup;
- propertyType.PropertyGroupId = new Lazy(() => tempGroup.Id);
- }
- }
- }
-
- //Run through all PropertyTypes to insert or update entries
- foreach (var propertyType in entity.PropertyTypes)
- {
- var tabId = propertyType.PropertyGroupId != null ? propertyType.PropertyGroupId.Value : default(int);
- //If the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias
- if (propertyType.DataTypeDefinitionId == 0 || propertyType.DataTypeDefinitionId == default(int))
- {
- AssignDataTypeFromPropertyEditor(propertyType);
- }
-
- //validate the alias!
- ValidateAlias(propertyType);
-
- var propertyTypeDto = propertyGroupFactory.BuildPropertyTypeDto(tabId, propertyType);
- int typePrimaryKey = propertyType.HasIdentity
- ? Database.Update(propertyTypeDto)
- : Convert.ToInt32(Database.Insert(propertyTypeDto));
- if (propertyType.HasIdentity == false)
- propertyType.Id = typePrimaryKey; //Set Id on new PropertyType
- }
- }
-
- protected IEnumerable GetAllowedContentTypeIds(int id)
- {
- var sql = Sql()
- .SelectAll()
- .From()
- .LeftJoin()
- .On(left => left.AllowedId, right => right.NodeId)
- .Where(x => x.Id == id);
-
- var allowedContentTypeDtos = Database.Fetch(sql);
- return allowedContentTypeDtos.Select(x => new ContentTypeSort(new Lazy(() => x.AllowedId), x.SortOrder, x.ContentTypeDto.Alias)).ToList();
- }
-
- protected PropertyGroupCollection GetPropertyGroupCollection(int id, DateTime createDate, DateTime updateDate)
- {
- var sql = Sql()
- .SelectAll()
- .From()
- .LeftJoin()
- .On(left => left.Id, right => right.PropertyTypeGroupId)
- .LeftJoin()
- .On(left => left.DataTypeId, right => right.DataTypeId)
- .Where(x => x.ContentTypeNodeId == id)
- .OrderBy(x => x.Id);
-
-
- var dtos = Database
- .Fetch(sql);
-
- var propertyGroupFactory = new PropertyGroupFactory(id, createDate, updateDate, CreatePropertyType);
- var propertyGroups = propertyGroupFactory.BuildEntity(dtos);
- return new PropertyGroupCollection(propertyGroups);
- }
-
- protected PropertyTypeCollection GetPropertyTypeCollection(int id, DateTime createDate, DateTime updateDate)
- {
- var sql = Sql()
- .SelectAll()
- .From()
- .InnerJoin()
- .On(left => left.DataTypeId, right => right.DataTypeId)
- .Where(x => x.ContentTypeId == id);
-
- var dtos = Database.Fetch(sql);
-
- //TODO Move this to a PropertyTypeFactory
- var list = new List();
- foreach (var dto in dtos.Where(x => (x.PropertyTypeGroupId > 0) == false))
- {
- var propType = CreatePropertyType(dto.DataTypeDto.PropertyEditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias);
- propType.DataTypeDefinitionId = dto.DataTypeId;
- propType.Description = dto.Description;
- propType.Id = dto.Id;
- propType.Key = dto.UniqueId;
- propType.Name = dto.Name;
- propType.Mandatory = dto.Mandatory;
- propType.SortOrder = dto.SortOrder;
- propType.ValidationRegExp = dto.ValidationRegExp;
- propType.CreateDate = createDate;
- propType.UpdateDate = updateDate;
- list.Add(propType);
- }
- //Reset dirty properties
- Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false));
-
- return new PropertyTypeCollection(list);
- }
-
- protected void ValidateAlias(PropertyType pt)
- {
- Mandate.That(string.IsNullOrEmpty(pt.Alias) == false,
- () =>
- {
- var message =
- string.Format(
- "{0} '{1}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.",
- "Property Type",
- pt.Name);
- var exception = new InvalidOperationException(message);
-
- Logger.Error>(message, exception);
- throw exception;
- });
- }
-
- protected void ValidateAlias(TEntity entity)
- {
- Mandate.That(string.IsNullOrEmpty(entity.Alias) == false,
- () =>
- {
- var message =
- string.Format(
- "{0} '{1}' cannot have an empty Alias. This is most likely due to invalid characters stripped from the Alias.",
- typeof(TEntity).Name,
- entity.Name);
- var exception = new InvalidOperationException(message);
-
- Logger.Error>(message, exception);
- throw exception;
- });
- }
-
- ///
- /// Try to set the data type id based on its ControlId
- ///
- ///
- private void AssignDataTypeFromPropertyEditor(PropertyType propertyType)
- {
- //we cannot try to assign a data type of it's empty
- if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false)
- {
- var sql = Sql()
- .SelectAll()
- .From()
- .Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias })
- .OrderBy(typeDto => typeDto.DataTypeId);
- var datatype = Database.FirstOrDefault(sql);
- //we cannot assign a data type if one was not found
- if (datatype != null)
- {
- propertyType.DataTypeDefinitionId = datatype.DataTypeId;
- }
- else
- {
- Logger.Warn>("Could not assign a data type for the property type " + propertyType.Alias + " since no data type was found with a property editor " + propertyType.PropertyEditorAlias);
- }
- }
- }
-
- internal static class ContentTypeQueryMapper
- {
-
- public class AssociatedTemplate
- {
- public AssociatedTemplate(int templateId, string @alias, string templateName)
- {
- TemplateId = templateId;
- Alias = alias;
- TemplateName = templateName;
- }
-
- public int TemplateId { get; set; }
- public string Alias { get; set; }
- public string TemplateName { get; set; }
-
- protected bool Equals(AssociatedTemplate other)
- {
- return TemplateId == other.TemplateId;
- }
-
- public override bool Equals(object obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
- return Equals((AssociatedTemplate)obj);
- }
-
- public override int GetHashCode()
- {
- return TemplateId;
- }
- }
-
- public static IEnumerable GetMediaTypes(
- Database db, ISqlSyntaxProvider sqlSyntax,
- TRepo contentTypeRepository)
- where TRepo : IReadRepository
- {
- IDictionary> allParentMediaTypeIds;
- var mediaTypes = MapMediaTypes(db, sqlSyntax, out allParentMediaTypeIds)
- .ToArray();
-
- MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds);
-
- return mediaTypes;
- }
-
- public static IEnumerable GetContentTypes(
- Database db, ISqlSyntaxProvider sqlSyntax,
- TRepo contentTypeRepository,
- ITemplateRepository templateRepository)
- where TRepo : IReadRepository
- {
- IDictionary> allAssociatedTemplates;
- IDictionary> allParentContentTypeIds;
- var contentTypes = MapContentTypes(db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds)
- .ToArray();
-
- if (contentTypes.Any())
- {
- MapContentTypeTemplates(
- contentTypes, db, contentTypeRepository, templateRepository, allAssociatedTemplates);
-
- MapContentTypeChildren(
- contentTypes, db, sqlSyntax, contentTypeRepository, allParentContentTypeIds);
- }
-
- return contentTypes;
- }
-
- internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes,
- Database db, ISqlSyntaxProvider sqlSyntax,
- TRepo contentTypeRepository,
- IDictionary> allParentContentTypeIds)
- where TRepo : IReadRepository
- {
- //NOTE: SQL call #2
-
- var ids = contentTypes.Select(x => x.Id).ToArray();
- IDictionary allPropGroups;
- IDictionary allPropTypes;
- MapGroupsAndProperties(ids, db, sqlSyntax, out allPropTypes, out allPropGroups);
-
- foreach (var contentType in contentTypes)
- {
- contentType.PropertyGroups = allPropGroups[contentType.Id];
- contentType.NoGroupPropertyTypes = allPropTypes[contentType.Id];
- }
-
- //NOTE: SQL call #3++
-
- if (allParentContentTypeIds != null)
- {
- var allParentIdsAsArray = allParentContentTypeIds.SelectMany(x => x.Value).Distinct().ToArray();
- if (allParentIdsAsArray.Any())
- {
- var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray();
-
- foreach (var contentType in contentTypes)
- {
- var entityId = contentType.Id;
-
- var parentContentTypes = allParentContentTypes.Where(x =>
- {
- var parentEntityId = x.Id;
-
- return allParentContentTypeIds[entityId].Contains(parentEntityId);
- });
- foreach (var parentContentType in parentContentTypes)
- {
- var result = contentType.AddContentType(parentContentType);
- //Do something if adding fails? (Should hopefully not be possible unless someone created a circular reference)
- }
-
- //on initial construction we don't want to have dirty properties tracked
- // http://issues.umbraco.org/issue/U4-1946
- ((Entity)contentType).ResetDirtyProperties(false);
- }
- }
- }
-
-
- }
-
- internal static void MapContentTypeTemplates(IContentType[] contentTypes,
- Database db,
- TRepo contentTypeRepository,
- ITemplateRepository templateRepository,
- IDictionary> associatedTemplates)
- where TRepo : IReadRepository
- {
- if (associatedTemplates == null || associatedTemplates.Any() == false) return;
-
- //NOTE: SQL call #3++
- //SEE: http://issues.umbraco.org/issue/U4-5174 to fix this
-
- var templateIds = associatedTemplates.SelectMany(x => x.Value).Select(x => x.TemplateId)
- .Distinct()
- .ToArray();
-
- var templates = (templateIds.Any()
- ? templateRepository.GetAll(templateIds)
- : Enumerable.Empty()).ToArray();
-
- foreach (var contentType in contentTypes)
- {
- var entityId = contentType.Id;
-
- var associatedTemplateIds = associatedTemplates[entityId].Select(x => x.TemplateId)
- .Distinct()
- .ToArray();
-
- contentType.AllowedTemplates = (associatedTemplateIds.Any()
- ? templates.Where(x => associatedTemplateIds.Contains(x.Id))
- : Enumerable.Empty()).ToArray();
- }
-
-
- }
-
- internal static IEnumerable MapMediaTypes(Database db, ISqlSyntaxProvider sqlSyntax,
- out IDictionary> parentMediaTypeIds)
- {
- Mandate.ParameterNotNull(db, "db");
-
- var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc,
- cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
- AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias,
- ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey,
- umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser,
- umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed,
- umbracoNode.uniqueID as nUniqueId
- FROM cmsContentType
- INNER JOIN umbracoNode
- ON cmsContentType.nodeId = umbracoNode.id
- LEFT JOIN (
- SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder
- FROM cmsContentTypeAllowedContentType
- INNER JOIN cmsContentType
- ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId
- ) AllowedTypes
- ON AllowedTypes.Id = cmsContentType.nodeId
- LEFT JOIN (
- SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId
- FROM cmsContentType2ContentType
- INNER JOIN umbracoNode
- ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @"
- ) ParentTypes
- ON ParentTypes.childContentTypeId = cmsContentType.nodeId
- WHERE (umbracoNode.nodeObjectType = @nodeObjectType)
- ORDER BY ctId";
-
- var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType) });
-
- if (result.Any() == false)
- {
- parentMediaTypeIds = null;
- return Enumerable.Empty();
- }
-
- parentMediaTypeIds = new Dictionary>();
- var mappedMediaTypes = new List();
-
- //loop through each result and fill in our required values, each row will contain different requried data than the rest.
- // it is much quicker to iterate each result and populate instead of looking up the values over and over in the result like
- // we used to do.
- var queue = new Queue(result);
- var currAllowedContentTypes = new List();
-
- while (queue.Count > 0)
- {
- var ct = queue.Dequeue();
-
- //check for allowed content types
- int? allowedCtId = ct.ctaAllowedId;
- int? allowedCtSort = ct.ctaSortOrder;
- string allowedCtAlias = ct.ctaAlias;
- if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null)
- {
- var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias);
- if (currAllowedContentTypes.Contains(ctSort) == false)
- {
- currAllowedContentTypes.Add(ctSort);
- }
- }
-
- //always ensure there's a list for this content type
- if (parentMediaTypeIds.ContainsKey(ct.ctId) == false)
- parentMediaTypeIds[ct.ctId] = new List();
-
- //check for parent ids and assign to the outgoing collection
- int? parentId = ct.chtParentId;
- if (parentId.HasValue)
- {
- var associatedParentIds = parentMediaTypeIds[ct.ctId];
- if (associatedParentIds.Contains(parentId.Value) == false)
- associatedParentIds.Add(parentId.Value);
- }
-
- if (queue.Count == 0 || queue.Peek().ctId != ct.ctId)
- {
- //it's the last in the queue or the content type is changing (moving to the next one)
- var mediaType = CreateForMapping(ct, currAllowedContentTypes);
- mappedMediaTypes.Add(mediaType);
-
- //Here we need to reset the current variables, we're now collecting data for a different content type
- currAllowedContentTypes = new List();
- }
- }
-
- return mappedMediaTypes;
- }
-
- private static IMediaType CreateForMapping(dynamic currCt, List currAllowedContentTypes)
- {
- // * create the DTO object
- // * create the content type object
- // * map the allowed content types
- // * add to the outgoing list
-
- var contentTypeDto = new ContentTypeDto
- {
- Alias = currCt.ctAlias,
- AllowAtRoot = currCt.ctAllowAtRoot,
- Description = currCt.ctDesc,
- Icon = currCt.ctIcon,
- IsContainer = currCt.ctIsContainer,
- NodeId = currCt.ctId,
- PrimaryKey = currCt.ctPk,
- Thumbnail = currCt.ctThumb,
- //map the underlying node dto
- NodeDto = new NodeDto
- {
- CreateDate = currCt.nCreateDate,
- Level = (short)currCt.nLevel,
- NodeId = currCt.ctId,
- NodeObjectType = currCt.nObjectType,
- ParentId = currCt.nParentId,
- Path = currCt.nPath,
- SortOrder = currCt.nSortOrder,
- Text = currCt.nName,
- Trashed = currCt.nTrashed,
- UniqueId = currCt.nUniqueId,
- UserId = currCt.nUser
- }
- };
-
- //now create the content type object
-
- var factory = new ContentTypeFactory();
- var mediaType = factory.BuildMediaTypeEntity(contentTypeDto);
-
- //map the allowed content types
- mediaType.AllowedContentTypes = currAllowedContentTypes;
-
- return mediaType;
- }
-
- internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax,
- out IDictionary> associatedTemplates,
- out IDictionary> parentContentTypeIds)
- {
- Mandate.ParameterNotNull(db, "db");
-
- var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId,
- cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc,
- cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
- AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias,
- ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey,
- umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser,
- umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed,
- umbracoNode.uniqueID as nUniqueId,
- Template.alias as tAlias, Template.nodeId as tId,Template.text as tText
- FROM cmsContentType
- INNER JOIN umbracoNode
- ON cmsContentType.nodeId = umbracoNode.id
- LEFT JOIN cmsDocumentType
- ON cmsDocumentType.contentTypeNodeId = cmsContentType.nodeId
- LEFT JOIN (
- SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder
- FROM cmsContentTypeAllowedContentType
- INNER JOIN cmsContentType
- ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId
- ) AllowedTypes
- ON AllowedTypes.Id = cmsContentType.nodeId
- LEFT JOIN (
- SELECT * FROM cmsTemplate
- INNER JOIN umbracoNode
- ON cmsTemplate.nodeId = umbracoNode.id
- ) as Template
- ON Template.nodeId = cmsDocumentType.templateNodeId
- LEFT JOIN (
- SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId
- FROM cmsContentType2ContentType
- INNER JOIN umbracoNode
- ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @"
- ) ParentTypes
- ON ParentTypes.childContentTypeId = cmsContentType.nodeId
- WHERE (umbracoNode.nodeObjectType = @nodeObjectType)
- ORDER BY ctId";
-
- var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType)});
-
- if (result.Any() == false)
- {
- parentContentTypeIds = null;
- associatedTemplates = null;
- return Enumerable.Empty();
- }
-
- parentContentTypeIds = new Dictionary>();
- associatedTemplates = new Dictionary>();
- var mappedContentTypes = new List();
-
- var queue = new Queue(result);
- var currDefaultTemplate = -1;
- var currAllowedContentTypes = new List();
- while (queue.Count > 0)
- {
- var ct = queue.Dequeue();
-
- //check for default templates
- bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault);
- int? templateId = ct.dtTemplateId;
- if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue)
- {
- currDefaultTemplate = templateId.Value;
- }
-
- //always ensure there's a list for this content type
- if (associatedTemplates.ContainsKey(ct.ctId) == false)
- associatedTemplates[ct.ctId] = new List();
-
- //check for associated templates and assign to the outgoing collection
- if (ct.tId != null)
- {
- var associatedTemplate = new AssociatedTemplate(ct.tId, ct.tAlias, ct.tText);
- var associatedList = associatedTemplates[ct.ctId];
-
- if (associatedList.Contains(associatedTemplate) == false)
- associatedList.Add(associatedTemplate);
- }
-
- //check for allowed content types
- int? allowedCtId = ct.ctaAllowedId;
- int? allowedCtSort = ct.ctaSortOrder;
- string allowedCtAlias = ct.ctaAlias;
- if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null)
- {
- var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias);
- if (currAllowedContentTypes.Contains(ctSort) == false)
- {
- currAllowedContentTypes.Add(ctSort);
- }
- }
-
- //always ensure there's a list for this content type
- if (parentContentTypeIds.ContainsKey(ct.ctId) == false)
- parentContentTypeIds[ct.ctId] = new List();
-
- //check for parent ids and assign to the outgoing collection
- int? parentId = ct.chtParentId;
- if (parentId.HasValue)
- {
- var associatedParentIds = parentContentTypeIds[ct.ctId];
-
- if (associatedParentIds.Contains(parentId.Value) == false)
- associatedParentIds.Add(parentId.Value);
- }
-
- if (queue.Count == 0 || queue.Peek().ctId != ct.ctId)
- {
- //it's the last in the queue or the content type is changing (moving to the next one)
- var contentType = CreateForMapping(ct, currAllowedContentTypes, currDefaultTemplate);
- mappedContentTypes.Add(contentType);
-
- //Here we need to reset the current variables, we're now collecting data for a different content type
- currDefaultTemplate = -1;
- currAllowedContentTypes = new List();
- }
- }
-
- return mappedContentTypes;
- }
-
- private static IContentType CreateForMapping(dynamic currCt, List currAllowedContentTypes, int currDefaultTemplate)
- {
- // * set the default template to the first one if a default isn't found
- // * create the DTO object
- // * create the content type object
- // * map the allowed content types
- // * add to the outgoing list
-
- var dtDto = new ContentTypeTemplateDto
- {
- //create the content type dto
- ContentTypeDto = new ContentTypeDto
- {
- Alias = currCt.ctAlias,
- AllowAtRoot = currCt.ctAllowAtRoot,
- Description = currCt.ctDesc,
- Icon = currCt.ctIcon,
- IsContainer = currCt.ctIsContainer,
- NodeId = currCt.ctId,
- PrimaryKey = currCt.ctPk,
- Thumbnail = currCt.ctThumb,
- //map the underlying node dto
- NodeDto = new NodeDto
- {
- CreateDate = currCt.nCreateDate,
- Level = (short)currCt.nLevel,
- NodeId = currCt.ctId,
- NodeObjectType = currCt.nObjectType,
- ParentId = currCt.nParentId,
- Path = currCt.nPath,
- SortOrder = currCt.nSortOrder,
- Text = currCt.nName,
- Trashed = currCt.nTrashed,
- UniqueId = currCt.nUniqueId,
- UserId = currCt.nUser
- }
- },
- ContentTypeNodeId = currCt.ctId,
- IsDefault = currDefaultTemplate != -1,
- TemplateNodeId = currDefaultTemplate != -1 ? currDefaultTemplate : 0,
- };
-
- //now create the content type object
-
- var factory = new ContentTypeFactory();
- var contentType = factory.BuildContentTypeEntity(dtDto.ContentTypeDto);
-
- // NOTE
- // that was done by the factory but makes little sense, moved here, so
- // now we have to reset dirty props again (as the factory does it) and yet,
- // we are not managing allowed templates... the whole thing is weird.
- ((ContentType)contentType).DefaultTemplateId = dtDto.TemplateNodeId;
- contentType.ResetDirtyProperties(false);
-
- //map the allowed content types
- contentType.AllowedContentTypes = currAllowedContentTypes;
-
- return contentType;
- }
-
- internal static void MapGroupsAndProperties(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax,
- out IDictionary allPropertyTypeCollection,
- out IDictionary allPropertyGroupCollection)
- {
- allPropertyGroupCollection = new Dictionary();
- allPropertyTypeCollection = new Dictionary