diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs
index 0c1a202b66..92f8529bbe 100644
--- a/src/Umbraco.Core/Cache/CacheKeys.cs
+++ b/src/Umbraco.Core/Cache/CacheKeys.cs
@@ -28,10 +28,7 @@ namespace Umbraco.Core.Cache
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string MacroCacheKey = "UmbracoMacroCache";
- public const string MacroHtmlCacheKey = "macroHtml_";
- public const string MacroControlCacheKey = "macroControl_";
- public const string MacroHtmlDateAddedCacheKey = "macroHtml_DateAdded_";
- public const string MacroControlDateAddedCacheKey = "macroControl_DateAdded_";
+ public const string MacroContentCacheKey = "macroContent_"; // for macro contents
[UmbracoWillObsolete("This cache key is only used for legacy 'library' member caching, remove in v8")]
public const string MemberLibraryCacheKey = "UL_GetMember";
diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
index e9b63976d2..00f1320525 100644
--- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
@@ -6,79 +6,115 @@ using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
///
- /// A base class for cache refreshers to inherit from that ensures the correct events are raised
- /// when cache refreshing occurs.
+ /// A base class for cache refreshers that handles events.
///
- /// The real cache refresher type, this is used for raising strongly typed events
+ /// The actual cache refresher type.
+ /// The actual cache refresher type is used for strongly typed events.
public abstract class CacheRefresherBase : ICacheRefresher
- where TInstanceType : ICacheRefresher
+ where TInstanceType : class, ICacheRefresher
{
+ ///
+ /// Initializes a new instance of the .
+ ///
+ /// A cache helper.
protected CacheRefresherBase(CacheHelper cacheHelper)
{
CacheHelper = cacheHelper;
}
///
- /// An event that is raised when cache is updated on an individual server
+ /// Triggers when the cache is updated on the server.
///
///
- /// This event will fire on each server configured for an Umbraco project whenever a cache refresher
- /// is updated.
+ /// Triggers on each server configured for an Umbraco project whenever a cache refresher is updated.
///
public static event TypedEventHandler CacheUpdated;
- ///
- /// Raises the event
- ///
- ///
- ///
- protected static void OnCacheUpdated(TInstanceType sender, CacheRefresherEventArgs args)
- {
- if (CacheUpdated != null)
- {
- CacheUpdated(sender, args);
- }
- }
+ #region Define
///
- /// Returns the real instance of the object ('this') for use in strongly typed events
+ /// Gets the typed 'this' for events.
///
protected abstract TInstanceType Instance { get; }
- public abstract Guid UniqueIdentifier { get; }
+ ///
+ /// Gets the unique identifier of the refresher.
+ ///
+ public abstract Guid RefresherUniqueId { get; }
+ ///
+ /// Gets the name of the refresher.
+ ///
public abstract string Name { get; }
- protected CacheHelper CacheHelper { get; }
+ #endregion
+ #region Refresher
+
+ ///
+ /// Refreshes all entities.
+ ///
public virtual void RefreshAll()
{
OnCacheUpdated(Instance, new CacheRefresherEventArgs(null, MessageType.RefreshAll));
}
+ ///
+ /// Refreshes an entity.
+ ///
+ /// The entity's identifier.
public virtual void Refresh(int id)
{
OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RefreshById));
}
- public virtual void Remove(int id)
- {
- OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RemoveById));
- }
-
+ ///
+ /// Refreshes an entity.
+ ///
+ /// The entity's identifier.
public virtual void Refresh(Guid id)
{
OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RefreshById));
}
///
- /// Clears the cache for all repository entities of this type
+ /// Removes an entity.
///
- ///
- internal void ClearAllIsolatedCacheByEntityType()
+ /// The entity's identifier.
+ public virtual void Remove(int id)
+ {
+ OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RemoveById));
+ }
+
+ #endregion
+
+ #region Protected
+
+ ///
+ /// Gets the cache helper.
+ ///
+ protected CacheHelper CacheHelper { get; }
+
+ ///
+ /// Clears the cache for all repository entities of a specified type.
+ ///
+ /// The type of the entities.
+ protected void ClearAllIsolatedCacheByEntityType()
where TEntity : class, IAggregateRoot
{
CacheHelper.IsolatedRuntimeCache.ClearCache();
}
+
+ ///
+ /// Raises the CacheUpdated event.
+ ///
+ /// The event sender.
+ /// The event arguments.
+ protected static void OnCacheUpdated(TInstanceType sender, CacheRefresherEventArgs args)
+ {
+ CacheUpdated?.Invoke(sender, args);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/CacheRefreshersResolver.cs b/src/Umbraco.Core/Cache/CacheRefreshersResolver.cs
index 4f91739068..1c2cff208f 100644
--- a/src/Umbraco.Core/Cache/CacheRefreshersResolver.cs
+++ b/src/Umbraco.Core/Cache/CacheRefreshersResolver.cs
@@ -38,7 +38,7 @@ namespace Umbraco.Core.Cache
/// The value of the type uniquely identified by .
public ICacheRefresher GetById(Guid id)
{
- return Values.FirstOrDefault(x => x.UniqueIdentifier == id);
+ return Values.FirstOrDefault(x => x.RefresherUniqueId == id);
}
}
diff --git a/src/Umbraco.Core/Cache/ICacheRefresher.cs b/src/Umbraco.Core/Cache/ICacheRefresher.cs
index 0117681ecd..291b59dadd 100644
--- a/src/Umbraco.Core/Cache/ICacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/ICacheRefresher.cs
@@ -8,7 +8,7 @@ namespace Umbraco.Core.Cache
///
public interface ICacheRefresher
{
- Guid UniqueIdentifier { get; }
+ Guid RefresherUniqueId { get; }
string Name { get; }
void RefreshAll();
void Refresh(int id);
diff --git a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs
index a2ccbd30ac..215c79aaa4 100644
--- a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs
@@ -8,7 +8,7 @@
///
/// Refreshes, clears, etc... any cache based on the information provided in the json
///
- ///
- void Refresh(string jsonPayload);
+ ///
+ void Refresh(string json);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs
index e15fc09355..322e65654c 100644
--- a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs
@@ -3,12 +3,12 @@
///
/// A cache refresher that supports refreshing cache based on a custom payload
///
- interface IPayloadCacheRefresher : IJsonCacheRefresher
+ interface IPayloadCacheRefresher : IJsonCacheRefresher
{
///
/// Refreshes, clears, etc... any cache based on the information provided in the payload
///
- ///
- void Refresh(object payload);
+ ///
+ void Refresh(TPayload[] payloads);
}
}
diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs
index ff4276e158..efd3e37189 100644
--- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs
@@ -3,22 +3,27 @@
namespace Umbraco.Core.Cache
{
///
- /// Provides a base class for "json" cache refreshers.
+ /// A base class for "json" cache refreshers.
///
- /// The actual cache refresher type.
- /// Ensures that the correct events are raised when cache refreshing occurs.
- public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher
- where TInstance : ICacheRefresher
+ /// The actual cache refresher type.
+ /// The actual cache refresher type is used for strongly typed events.
+ public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher
+ where TInstanceType : class, ICacheRefresher
{
+ ///
+ /// Initializes a new instance of the .
+ ///
+ /// A cache helper.
protected JsonCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper)
- {
- }
+ { }
+ ///
+ /// Refreshes as specified by a json payload.
+ ///
+ /// The json payload.
public virtual void Refresh(string json)
{
OnCacheUpdated(Instance, new CacheRefresherEventArgs(json, MessageType.RefreshByJson));
}
-
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs
index b34d49b729..52f95208a9 100644
--- a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs
@@ -1,20 +1,39 @@
-using Umbraco.Core.Sync;
+using Newtonsoft.Json;
+using Umbraco.Core.Sync;
namespace Umbraco.Core.Cache
{
///
- /// Provides a base class for "payload" cache refreshers.
+ /// A base class for "payload" class refreshers.
///
- /// The actual cache refresher type.
- /// Ensures that the correct events are raised when cache refreshing occurs.
- public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher
- where TInstance : ICacheRefresher
+ /// The actual cache refresher type.
+ /// The payload type.
+ /// The actual cache refresher type is used for strongly typed events.
+ public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher
+ where TInstanceType : class, ICacheRefresher
{
+ ///
+ /// Initializes a new instance of the .
+ ///
+ /// A cache helper.
protected PayloadCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper)
+ { }
+
+ #region Json
+
+ ///
+ /// Deserializes a json payload into an object payload.
+ ///
+ /// The json payload.
+ /// The deserialized object payload.
+ protected virtual TPayload[] Deserialize(string json)
{
+ return JsonConvert.DeserializeObject(json);
}
- protected abstract object Deserialize(string json);
+ #endregion
+
+ #region Refresher
public override void Refresh(string json)
{
@@ -22,9 +41,15 @@ namespace Umbraco.Core.Cache
Refresh(payload);
}
- public virtual void Refresh(object payload)
+ ///
+ /// Refreshes as specified by a payload.
+ ///
+ /// The payload.
+ public virtual void Refresh(TPayload[] payloads)
{
- OnCacheUpdated(Instance, new CacheRefresherEventArgs(payload, MessageType.RefreshByPayload));
+ OnCacheUpdated(Instance, new CacheRefresherEventArgs(payloads, MessageType.RefreshByPayload));
}
+
+ #endregion
}
}
diff --git a/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs b/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs
index 36b7cdcd0e..c8277651a3 100644
--- a/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/TypedCacheRefresherBase.cs
@@ -3,17 +3,23 @@
namespace Umbraco.Core.Cache
{
///
- /// A base class for cache refreshers to inherit from that ensures the correct events are raised
- /// when cache refreshing occurs.
+ /// A base class for "typed" cache refreshers.
///
- /// The real cache refresher type, this is used for raising strongly typed events
- /// The entity type that this refresher can update cache for
+ /// The actual cache refresher type.
+ /// The entity type.
+ /// The actual cache refresher type is used for strongly typed events.
public abstract class TypedCacheRefresherBase : CacheRefresherBase, ICacheRefresher
- where TInstanceType : ICacheRefresher
+ where TInstanceType : class, ICacheRefresher
{
- protected TypedCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper)
- {
- }
+ ///
+ /// Initializes a new instance of the .
+ ///
+ /// A cache helper.
+ protected TypedCacheRefresherBase(CacheHelper cacheHelper)
+ : base(cacheHelper)
+ { }
+
+ #region Refresher
public virtual void Refresh(TEntityType instance)
{
@@ -24,5 +30,7 @@ namespace Umbraco.Core.Cache
{
OnCacheUpdated(Instance, new CacheRefresherEventArgs(instance, MessageType.RemoveByInstance));
}
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index 07a3307a77..4c90df9111 100644
--- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
///
/// The version comment.
- public static string CurrentComment => "alpha0001";
+ public static string CurrentComment => "alpha0002";
// Get the version of the Umbraco.Core.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs
index 6fd3f01fe2..29c083b4a2 100644
--- a/src/Umbraco.Core/Constants-ObjectTypes.cs
+++ b/src/Umbraco.Core/Constants-ObjectTypes.cs
@@ -114,6 +114,11 @@ namespace Umbraco.Core
///
public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560";
+ ///
+ /// Guid for a Member object.
+ ///
+ public static readonly Guid MemberGuid = new Guid("39EB0F98-B348-42A1-8662-E7EB18487560");
+
///
/// Guid for a Member Group object.
///
diff --git a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs
index 8caaaf56ae..59edb78be7 100644
--- a/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs
+++ b/src/Umbraco.Core/DependencyInjection/ServicesCompositionRoot.cs
@@ -18,10 +18,10 @@ namespace Umbraco.Core.DependencyInjection
// boot manager when running in a web context
container.Register();
- //the context
+ // register the service context
container.RegisterSingleton();
- //now the services...
+ // register the services
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
diff --git a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs
index c05800d2e0..bdda23ba3a 100644
--- a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs
+++ b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs
@@ -1,21 +1,18 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using Umbraco.Core.Macros;
namespace Umbraco.Core.Events
{
// Provides information on the macro that caused an error
- public class MacroErrorEventArgs : System.EventArgs
+ public class MacroErrorEventArgs : EventArgs
{
///
- /// Name of the faulting macro.
+ /// Name of the faulting macro.
///
public string Name { get; set; }
///
- /// Alias of the faulting macro.
+ /// Alias of the faulting macro.
///
public string Alias { get; set; }
@@ -36,5 +33,10 @@ namespace Umbraco.Core.Events
///
/// Macro error behaviour enum.
public MacroErrorBehaviour Behaviour { get; set; }
+
+ ///
+ /// The html code to display when Behavior is Content.
+ ///
+ public string Html { get; set; }
}
}
diff --git a/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs
index be63c36d13..dd3d506b23 100644
--- a/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs
+++ b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs
@@ -18,6 +18,12 @@
/// defined in Application_OnError. If no such error handler is defined
/// then you'll see the Yellow Screen Of Death (YSOD) error page.
///
- Throw
+ Throw,
+
+ ///
+ /// Silently eat the error and display the custom content reported in
+ /// the error event args
+ ///
+ Content
}
}
diff --git a/src/Umbraco.Core/Macros/PartialViewMacroResult.cs b/src/Umbraco.Core/Macros/PartialViewMacroResult.cs
deleted file mode 100644
index bb0d61ac45..0000000000
--- a/src/Umbraco.Core/Macros/PartialViewMacroResult.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-
-namespace Umbraco.Core.Macros
-{
- internal class PartialViewMacroResult
- {
- public PartialViewMacroResult()
- {
- }
-
- public PartialViewMacroResult(string result, Exception resultException)
- {
- Result = result;
- ResultException = resultException;
- }
-
- public string Result { get; set; }
- public Exception ResultException { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ContentTypeExtensions.cs b/src/Umbraco.Core/Models/ContentTypeExtensions.cs
index ea6a28eb37..87cd881794 100644
--- a/src/Umbraco.Core/Models/ContentTypeExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentTypeExtensions.cs
@@ -7,31 +7,48 @@ namespace Umbraco.Core.Models
internal static class ContentTypeExtensions
{
///
- /// Get all descendant content types
+ /// Gets all descendant content types of a specified content type.
///
- ///
- ///
- ///
- public static IEnumerable Descendants(this TItem contentType, IContentTypeServiceBase contentTypeService)
+ /// The content type.
+ /// The content type service.
+ /// The descendant content types.
+ /// Descendants corresponds to the parent-child relationship, and has
+ /// nothing to do with compositions, though a child should always be composed
+ /// of its parent.
+ public static IEnumerable Descendants(this TItem contentType, IContentTypeServiceBase contentTypeService)
where TItem : IContentTypeComposition
- {
- var descendants = contentTypeService.GetChildren(contentType.Id)
- .SelectRecursive(type => contentTypeService.GetChildren(type.Id));
- return descendants;
+ {
+ return contentTypeService.GetDescendants(contentType.Id, false);
}
///
- /// Get all descendant and self content types
+ /// Gets all descendant and self content types of a specified content type.
///
- ///
- ///
- ///
- public static IEnumerable DescendantsAndSelf(this TItem contentType, IContentTypeServiceBase contentTypeService)
+ /// The content type.
+ /// The content type service.
+ /// The descendant and self content types.
+ /// Descendants corresponds to the parent-child relationship, and has
+ /// nothing to do with compositions, though a child should always be composed
+ /// of its parent.
+ public static IEnumerable DescendantsAndSelf(this TItem contentType, IContentTypeServiceBase contentTypeService)
where TItem : IContentTypeComposition
{
- var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants(contentTypeService));
- return descendantsAndSelf;
+ return contentTypeService.GetDescendants(contentType.Id, true);
+ }
+
+ ///
+ /// Gets all content types directly or indirectly composed of a specified content type.
+ ///
+ /// The content type.
+ /// The content type service.
+ /// The content types directly or indirectly composed of the content type.
+ /// This corresponds to the composition relationship and has nothing to do
+ /// with the parent-child relationship, though a child should always be composed of
+ /// its parent.
+ public static IEnumerable ComposedOf(this TItem contentType, IContentTypeServiceBase contentTypeService)
+ where TItem : IContentTypeComposition
+ {
+ return contentTypeService.GetComposedOf(contentType.Id);
}
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
index 9baf0c1024..5e83c99c90 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Umbraco.Core.Cache;
namespace Umbraco.Core.Models.PublishedContent
{
@@ -21,23 +20,51 @@ namespace Umbraco.Core.Models.PublishedContent
// internal so it can be used by PublishedNoCache which does _not_ want to cache anything and so will never
// use the static cache getter PublishedContentType.GetPublishedContentType(alias) below - anything else
// should use it.
- internal PublishedContentType(IContentTypeComposition contentType)
+ internal PublishedContentType(IContentType contentType)
+ : this(PublishedItemType.Content, contentType)
+ { }
+
+ internal PublishedContentType(IMediaType mediaType)
+ : this(PublishedItemType.Media, mediaType)
+ { }
+
+ internal PublishedContentType(IMemberType memberType)
+ : this(PublishedItemType.Member, memberType)
+ { }
+
+ internal PublishedContentType(PublishedItemType itemType, IContentTypeComposition contentType)
{
Id = contentType.Id;
Alias = contentType.Alias;
+ ItemType = itemType;
_compositionAliases = new HashSet(contentType.CompositionAliases(), StringComparer.InvariantCultureIgnoreCase);
- _propertyTypes = contentType.CompositionPropertyTypes
- .Select(x => new PublishedPropertyType(this, x))
- .ToArray();
+ var propertyTypes = contentType.CompositionPropertyTypes
+ .Select(x => new PublishedPropertyType(this, x));
+ if (itemType == PublishedItemType.Member)
+ propertyTypes = WithMemberProperties(propertyTypes, this);
+ _propertyTypes = propertyTypes.ToArray();
InitializeIndexes();
}
+ // internal so it can be used for unit tests
+ internal PublishedContentType(int id, string alias, IEnumerable propertyTypes)
+ : this(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes)
+ { }
+
// internal so it can be used for unit tests
internal PublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes)
+ : this(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes)
+ { }
+
+ // internal so it can be used for unit tests
+ internal PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes)
{
Id = id;
Alias = alias;
+ ItemType = itemType;
_compositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase);
+ if (itemType == PublishedItemType.Member)
+ propertyTypes = WithMemberProperties(propertyTypes);
_propertyTypes = propertyTypes.ToArray();
foreach (var propertyType in _propertyTypes)
propertyType.ContentType = this;
@@ -59,20 +86,62 @@ namespace Umbraco.Core.Models.PublishedContent
}
}
+ // NOTE: code below defines and add custom, built-in, Umbraco properties for members
+ // unless they are already user-defined in the content type, then they are skipped
+
+ // fixme should have constants for these
+ private const int TextboxDataTypeDefinitionId = -88;
+ //private const int BooleanDataTypeDefinitionId = -49;
+ //private const int DatetimeDataTypeDefinitionId = -36;
+
+ static readonly Dictionary> BuiltinProperties = new Dictionary>
+ {
+ // fixme is this ok?
+ { "Email", Tuple.Create(TextboxDataTypeDefinitionId, Constants.PropertyEditors.TextboxAlias) },
+ { "Username", Tuple.Create(TextboxDataTypeDefinitionId, Constants.PropertyEditors.TextboxAlias) },
+ //{ "PasswordQuestion", Tuple.Create(TextboxDataTypeDefinitionId, Constants.PropertyEditors.TextboxAlias) },
+ //{ "Comments", Tuple.Create(TextboxDataTypeDefinitionId, Constants.PropertyEditors.TextboxAlias) },
+ //{ "IsApproved", Tuple.Create(BooleanDataTypeDefinitionId, Constants.PropertyEditors.BooleanEditorAlias) },
+ //{ "IsLockedOut", Tuple.Create(BooleanDataTypeDefinitionId, Constants.PropertyEditors.BooleanEditorAlias) },
+ //{ "LastLockoutDate", Tuple.Create(DatetimeDataTypeDefinitionId, Constants.PropertyEditors.DatetimeEditorAlias) },
+ //{ "CreateDate", Tuple.Create(DatetimeDataTypeDefinitionId, Constants.PropertyEditors.DatetimeEditorAlias) },
+ //{ "LastLoginDate", Tuple.Create(DatetimeDataTypeDefinitionId, Constants.PropertyEditors.DatetimeEditorAlias) },
+ //{ "LastPasswordChangeDate", Tuple.Create(DatetimeDataTypeDefinitionId, Constants.PropertyEditors.DatetimeEditorAlias) },
+ };
+
+ private static IEnumerable WithMemberProperties(IEnumerable propertyTypes,
+ PublishedContentType contentType = null)
+ {
+ var aliases = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var propertyType in propertyTypes)
+ {
+ aliases.Add(propertyType.PropertyTypeAlias);
+ yield return propertyType;
+ }
+
+ foreach (var kvp in BuiltinProperties.Where(kvp => aliases.Contains(kvp.Key) == false))
+ {
+ var propertyType = new PublishedPropertyType(kvp.Key, kvp.Value.Item1, kvp.Value.Item2, true);
+ if (contentType != null) propertyType.ContentType = contentType;
+ yield return propertyType;
+ }
+ }
+
#region Content type
public int Id { get; private set; }
+
public string Alias { get; private set; }
- public HashSet CompositionAliases { get { return _compositionAliases; } }
+
+ public PublishedItemType ItemType { get; private set; }
+
+ public HashSet CompositionAliases => _compositionAliases;
#endregion
#region Properties
- public IEnumerable PropertyTypes
- {
- get { return _propertyTypes; }
- }
+ public IEnumerable PropertyTypes => _propertyTypes;
// alias is case-insensitive
// this is the ONLY place where we compare ALIASES!
@@ -98,108 +167,5 @@ namespace Umbraco.Core.Models.PublishedContent
}
#endregion
-
- #region Cache
-
- // these methods are called by ContentTypeCacheRefresher and DataTypeCacheRefresher
-
- internal static void ClearAll()
- {
- Logging.LogHelper.Debug("Clear all.");
- // ok and faster to do it by types, assuming noone else caches PublishedContentType instances
- //ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_");
- ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes();
- }
-
- internal static void ClearContentType(int id)
- {
- Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id);
-
- // we don't support "get all" at the moment - so, cheating
- var all = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItemsByKeySearch("PublishedContentType_").ToArray();
-
- // the one we want to clear
- var clr = all.FirstOrDefault(x => x.Id == id);
- if (clr == null) return;
-
- // those that have that one in their composition aliases
- // note: CompositionAliases contains all recursive aliases
- var oth = all.Where(x => x.CompositionAliases.InvariantContains(clr.Alias)).Select(x => x.Id);
-
- // merge ids
- var ids = oth.Concat(new[] { clr.Id }).ToArray();
-
- // clear them all at once
- // we don't support "clear many at once" at the moment - so, cheating
- ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(
- (key, value) => ids.Contains(value.Id));
- }
-
- internal static void ClearDataType(int id)
- {
- Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id);
- // there is no recursion to handle here because a PublishedContentType contains *all* its
- // properties ie both its own properties and those that were inherited (it's based upon an
- // IContentTypeComposition) and so every PublishedContentType having a property based upon
- // the cleared data type, be it local or inherited, will be cleared.
- ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(
- (key, value) => value.PropertyTypes.Any(x => x.DataTypeId == id));
- }
-
- public static PublishedContentType Get(PublishedItemType itemType, string alias)
- {
- var key = string.Format("PublishedContentType_{0}_{1}",
- itemType.ToString().ToLowerInvariant(), alias.ToLowerInvariant());
-
- var type = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItem(key,
- () => CreatePublishedContentType(itemType, alias));
-
- return type;
- }
-
- private static PublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias)
- {
- if (GetPublishedContentTypeCallback != null)
- return GetPublishedContentTypeCallback(alias);
-
- IContentTypeComposition contentType;
- switch (itemType)
- {
- case PublishedItemType.Content:
- contentType = ApplicationContext.Current.Services.ContentTypeService.Get(alias);
- break;
- case PublishedItemType.Media:
- contentType = ApplicationContext.Current.Services.MediaTypeService.Get(alias);
- break;
- case PublishedItemType.Member:
- contentType = ApplicationContext.Current.Services.MemberTypeService.Get(alias);
- break;
- default:
- throw new ArgumentOutOfRangeException("itemType");
- }
-
- if (contentType == null)
- throw new Exception(string.Format("ContentTypeService failed to find a {0} type with alias \"{1}\".",
- itemType.ToString().ToLower(), alias));
-
- return new PublishedContentType(contentType);
- }
-
- // for unit tests - changing the callback must reset the cache obviously
- private static Func _getPublishedContentTypeCallBack;
- internal static Func GetPublishedContentTypeCallback
- {
- get { return _getPublishedContentTypeCallBack; }
- set
- {
- // see note above
- //ClearAll();
- ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheByKeySearch("PublishedContentType_");
-
- _getPublishedContentTypeCallBack = value;
- }
- }
-
- #endregion
}
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs
index f27b827cc3..b17c8a7dbb 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
@@ -107,14 +106,15 @@ namespace Umbraco.Core.Models.PublishedContent
/// The property type alias.
/// The datatype definition identifier.
/// The property editor alias.
+ /// A value indicating whether the property is an Umbraco-defined property.
///
/// The new published property type does not belong to a published content type.
/// The values of and are
/// assumed to be valid and consistent.
///
- internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias)
+ internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias, bool umbraco = false)
{
- // ContentType
+ // ContentType
// - in unit tests, to be set by PublishedContentType when creating it
// - in detached types, remains null
@@ -122,6 +122,7 @@ namespace Umbraco.Core.Models.PublishedContent
DataTypeId = dataTypeDefinitionId;
PropertyEditorAlias = propertyEditorAlias;
+ IsUmbraco = umbraco;
InitializeConverters();
}
@@ -139,17 +140,22 @@ namespace Umbraco.Core.Models.PublishedContent
///
/// Gets or sets the alias uniquely identifying the property type.
///
- public string PropertyTypeAlias { get; private set; }
+ public string PropertyTypeAlias { get; }
///
/// Gets or sets the identifier uniquely identifying the data type supporting the property type.
///
- public int DataTypeId { get; private set; }
+ public int DataTypeId { get; }
///
/// Gets or sets the alias uniquely identifying the property editor for the property type.
///
- public string PropertyEditorAlias { get; private set; }
+ public string PropertyEditorAlias { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the property is an Umbraco-defined property.
+ ///
+ internal bool IsUmbraco { get; private set; }
#endregion
@@ -168,11 +174,11 @@ namespace Umbraco.Core.Models.PublishedContent
//TODO: Look at optimizing this method, it gets run for every property type for the document being rendered at startup,
// every precious second counts!
- var converters = PropertyValueConvertersResolver.Current.Converters.ToArray();
+ var converters = PropertyValueConvertersResolver.Current.Converters.ToArray();
var defaultConvertersWithAttributes = PropertyValueConvertersResolver.Current.DefaultConverters;
_converter = null;
-
+
//get all converters for this property type
var foundConverters = converters.Where(x => x.IsConverter(this)).ToArray();
if (foundConverters.Length == 1)
@@ -199,7 +205,7 @@ namespace Umbraco.Core.Models.PublishedContent
ContentType.Alias, PropertyTypeAlias,
nonDefault[1].GetType().FullName, nonDefault[0].GetType().FullName));
}
- else
+ else
{
//we need to remove any converters that have been shadowed by another converter
var foundDefaultConvertersWithAttributes = defaultConvertersWithAttributes.Where(x => foundConverters.Contains(x.Item1));
@@ -210,7 +216,7 @@ namespace Umbraco.Core.Models.PublishedContent
if (nonShadowedDefaultConverters.Length == 1)
{
//assign to the single default converter
- _converter = nonShadowedDefaultConverters[0];
+ _converter = nonShadowedDefaultConverters[0];
}
else if (nonShadowedDefaultConverters.Length > 1)
{
@@ -223,7 +229,7 @@ namespace Umbraco.Core.Models.PublishedContent
nonShadowedDefaultConverters[1].GetType().FullName, nonShadowedDefaultConverters[0].GetType().FullName));
}
}
-
+
}
var converterMeta = _converter as IPropertyValueConverterMeta;
@@ -268,9 +274,9 @@ namespace Umbraco.Core.Models.PublishedContent
var attr = converter.GetType().GetCustomAttributes(false)
.FirstOrDefault(x => x.Value == value || x.Value == PropertyCacheValue.All);
- return attr == null ? PropertyCacheLevel.Request : attr.Level;
+ return attr?.Level ?? PropertyCacheLevel.Request;
}
-
+
// converts the raw value into the source value
// uses converters, else falls back to dark (& performance-wise expensive) magic
// source: the property raw value
@@ -278,13 +284,13 @@ namespace Umbraco.Core.Models.PublishedContent
public object ConvertDataToSource(object source, bool preview)
{
// use the converter else use dark (& performance-wise expensive) magic
- return _converter != null
- ? _converter.ConvertDataToSource(this, source, preview)
+ return _converter != null
+ ? _converter.ConvertDataToSource(this, source, preview)
: ConvertUsingDarkMagic(source);
}
// gets the source cache level
- public PropertyCacheLevel SourceCacheLevel { get { return _sourceCacheLevel; } }
+ public PropertyCacheLevel SourceCacheLevel => _sourceCacheLevel;
// converts the source value into the clr value
// uses converters, else returns the source value
@@ -295,12 +301,12 @@ namespace Umbraco.Core.Models.PublishedContent
// use the converter if any
// else just return the source value
return _converter != null
- ? _converter.ConvertSourceToObject(this, source, preview)
+ ? _converter.ConvertSourceToObject(this, source, preview)
: source;
}
// gets the value cache level
- public PropertyCacheLevel ObjectCacheLevel { get { return _objectCacheLevel; } }
+ public PropertyCacheLevel ObjectCacheLevel => _objectCacheLevel;
// converts the source value into the xpath value
// uses the converter else returns the source value as a string
@@ -322,7 +328,7 @@ namespace Umbraco.Core.Models.PublishedContent
}
// gets the xpath cache level
- public PropertyCacheLevel XPathCacheLevel { get { return _xpathCacheLevel; } }
+ public PropertyCacheLevel XPathCacheLevel => _xpathCacheLevel;
internal static object ConvertUsingDarkMagic(object source)
{
@@ -356,23 +362,17 @@ namespace Umbraco.Core.Models.PublishedContent
}
// gets the property CLR type
- public Type ClrType { get { return _clrType; } }
+ public Type ClrType => _clrType;
#endregion
-
-
#region Detached
private PropertyCacheLevel _sourceCacheLevelReduced = 0;
private PropertyCacheLevel _objectCacheLevelReduced = 0;
private PropertyCacheLevel _xpathCacheLevelReduced = 0;
- internal bool IsDetachedOrNested
- {
- // enough to test source
- get { return _sourceCacheLevelReduced != 0; }
- }
+ internal bool IsDetachedOrNested => _sourceCacheLevelReduced != 0;
///
/// Creates a detached clone of this published property type.
@@ -389,13 +389,13 @@ namespace Umbraco.Core.Models.PublishedContent
throw new Exception("PublishedPropertyType is already detached/nested.");
var detached = new PublishedPropertyType(this);
- detached._sourceCacheLevel
- = detached._objectCacheLevel
- = detached._xpathCacheLevel
+ detached._sourceCacheLevel
+ = detached._objectCacheLevel
+ = detached._xpathCacheLevel
= PropertyCacheLevel.Content;
// set to none to a) indicate it's detached / nested and b) make sure any nested
// types switch all their cache to .Content
- detached._sourceCacheLevelReduced
+ detached._sourceCacheLevelReduced
= detached._objectCacheLevelReduced
= detached._xpathCacheLevelReduced
= PropertyCacheLevel.None;
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/RefactorXmlColumns.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/RefactorXmlColumns.cs
new file mode 100644
index 0000000000..a3f8fa63a3
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionEight/RefactorXmlColumns.cs
@@ -0,0 +1,95 @@
+using System.Collections.Generic;
+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 RefactorXmlColumns : MigrationBase
+ {
+ public RefactorXmlColumns(ILogger logger)
+ : base(logger)
+ { }
+
+ public override void Up()
+ {
+ if (ColumnExists("cmsContentXml", "Rv") == false)
+ Alter.Table("cmsContentXml").AddColumn("Rv").AsInt64().NotNullable().WithDefaultValue(0);
+
+ if (ColumnExists("cmsPreviewXml", "Rv") == false)
+ Alter.Table("cmsPreviewXml").AddColumn("Rv").AsInt64().NotNullable().WithDefaultValue(0);
+
+ // remove the any PK_ and the FK_ to cmsContentVersion.VersionId
+ if (DatabaseType.IsMySql())
+ {
+ Delete.PrimaryKey("PK_cmsPreviewXml").FromTable("cmsPreviewXml");
+
+ Delete.ForeignKey().FromTable("cmsPreviewXml").ForeignColumn("VersionId")
+ .ToTable("cmsContentVersion").PrimaryColumn("VersionId");
+ }
+ else
+ {
+ var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray();
+ var dups = new List();
+ foreach (var c in constraints.Where(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("PK_")))
+ {
+ var keyName = c.Item3.ToLowerInvariant();
+ if (dups.Contains(keyName))
+ {
+ Logger.Warn("Duplicate constraint " + c.Item3);
+ continue;
+ }
+ dups.Add(keyName);
+ Delete.PrimaryKey(c.Item3).FromTable(c.Item1);
+ }
+ foreach (var c in constraints.Where(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("FK_cmsPreviewXml_cmsContentVersion")))
+ {
+ Delete.ForeignKey().FromTable("cmsPreviewXml").ForeignColumn("VersionId")
+ .ToTable("cmsContentVersion").PrimaryColumn("VersionId");
+ }
+ }
+
+ if (ColumnExists("cmsPreviewXml", "Timestamp"))
+ Delete.Column("Timestamp").FromTable("cmsPreviewXml");
+
+ if (ColumnExists("cmsPreviewXml", "VersionId"))
+ {
+ RemoveDuplicates();
+ Delete.Column("VersionId").FromTable("cmsPreviewXml");
+ }
+
+ // re-create the primary key
+ Create.PrimaryKey("PK_cmsPreviewXml")
+ .OnTable("cmsPreviewXml")
+ .Columns(new[] { "nodeId" });
+ }
+
+ public override void Down()
+ {
+ throw new DataLossException("Downgrading is not supported.");
+
+ //if (Exists("cmsContentXml", "Rv"))
+ // Delete.Column("Rv").FromTable("cmsContentXml");
+ //if (Exists("cmsPreviewXml", "Rv"))
+ // Delete.Column("Rv").FromTable("cmsContentXml");
+ }
+
+ private bool ColumnExists(string tableName, string columnName)
+ {
+ // that's ok even on MySql
+ var columns = SqlSyntax.GetColumnsInSchema(Context.Database).Distinct().ToArray();
+ return columns.Any(x => x.TableName.InvariantEquals(tableName) && x.ColumnName.InvariantEquals(columnName));
+ }
+
+ private void RemoveDuplicates()
+ {
+ const string sql = @"delete from cmsPreviewXml where versionId in (
+select cmsPreviewXml.versionId from cmsPreviewXml
+join cmsDocument on cmsPreviewXml.versionId=cmsDocument.versionId
+where cmsDocument.newest <> 1)";
+
+ Context.Database.Execute(sql);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs
index 0689117d9b..a85e865c49 100644
--- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs
+++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
@@ -24,5 +25,12 @@ namespace Umbraco.Core.Persistence.Querying
///
IEnumerable> GetWhereClauses();
+ ///
+ /// Adds a where-in clause to the query
+ ///
+ ///
+ ///
+ /// This instance so calls to this method are chainable
+ IQuery WhereIn(Expression> fieldSelector, IEnumerable values);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs
index c51b451c21..4c58e9e122 100644
--- a/src/Umbraco.Core/Persistence/Querying/Query.cs
+++ b/src/Umbraco.Core/Persistence/Querying/Query.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
@@ -41,6 +42,18 @@ namespace Umbraco.Core.Persistence.Querying
return this;
}
+ public virtual IQuery WhereIn(Expression> fieldSelector, IEnumerable values)
+ {
+ if (fieldSelector != null)
+ {
+ var expressionHelper = new ModelToSqlExpressionHelper(_sqlSyntax, _mappingResolver);
+ string whereExpression = expressionHelper.Visit(fieldSelector);
+
+ _wheres.Add(new Tuple(whereExpression + " IN (@values)", new object[] { new { @values = values } }));
+ }
+ return this;
+ }
+
///
/// Returns all translated where clauses and their sql parameters
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
index 17e6664e35..ff85fff35f 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
@@ -24,14 +24,12 @@ namespace Umbraco.Core.Persistence.Repositories
///
/// Represents a repository for doing CRUD operations for .
///
- internal class ContentRepository : RecycleBinRepository, IContentRepository
+ internal class ContentRepository : RecycleBinRepository, IContentRepository
{
private readonly IContentTypeRepository _contentTypeRepository;
private readonly ITemplateRepository _templateRepository;
private readonly ITagRepository _tagRepository;
private readonly CacheHelper _cacheHelper;
- private readonly ContentPreviewRepository _contentPreviewRepository;
- private readonly ContentXmlRepository _contentXmlRepository;
public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection, IMappingResolver mappingResolver)
: base(work, cacheHelper, logger, contentSection, mappingResolver)
@@ -43,12 +41,12 @@ namespace Umbraco.Core.Persistence.Repositories
_templateRepository = templateRepository;
_tagRepository = tagRepository;
_cacheHelper = cacheHelper;
- _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, mappingResolver);
- _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, mappingResolver);
EnsureUniqueNaming = true;
}
+ protected override ContentRepository Instance => this;
+
public bool EnsureUniqueNaming { get; set; }
#region Overrides of RepositoryBase
@@ -233,7 +231,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected override void PerformDeleteVersion(int id, Guid versionId)
{
- Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
+ // raise event first else potential FK issues
+ OnUowRemovingVersion(new UnitOfWorkVersionEventArgs(UnitOfWork, id, versionId));
+
Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId });
Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
@@ -245,6 +245,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected override void PersistDeletedItem(IContent entity)
{
+ // raise event first else potential FK issues
+ OnUowRemovingEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity));
+
//We need to clear out all access rules but we need to do this in a manual way since
// nothing in that table is joined to a content id
var subQuery = Sql()
@@ -372,6 +375,8 @@ namespace Umbraco.Core.Persistence.Repositories
((Content)entity).PublishedVersionGuid = dto.VersionId;
}
+ OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity));
+
entity.ResetDirtyProperties();
}
@@ -551,6 +556,8 @@ namespace Umbraco.Core.Persistence.Repositories
content.PublishedVersionGuid = default(Guid);
}
+ OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity));
+
entity.ResetDirtyProperties();
}
@@ -682,16 +689,16 @@ namespace Umbraco.Core.Persistence.Repositories
///
/// An Enumerable list of objects
public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords,
- string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null)
+ string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null, bool newest = true)
{
+ var filterSql = Sql();
+ if (newest)
+ filterSql.Append("AND (cmsDocument.newest = 1)");
- var filterSql = Sql().Append("AND (cmsDocument.newest = 1)");
if (filter != null)
{
foreach (var filterClaus in filter.GetWhereClauses())
- {
filterSql.Append($"AND ({filterClaus.Item1})", filterClaus.Item2);
- }
}
return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords,
@@ -896,151 +903,5 @@ WHERE (@path LIKE {5})",
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/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
index 676fd4db2a..11a98c678e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
@@ -56,26 +56,6 @@ namespace Umbraco.Core.Persistence.Repositories
///
IEnumerable GetPermissionsForEntity(int entityId);
- ///
- /// Used to add/update published xml for the content item
- ///
- ///
- ///
- void AddOrUpdateContentXml(IContent content, Func xml);
-
- ///
- /// Used to remove the content xml for a content item
- ///
- ///
- void DeleteContentXml(IContent content);
-
- ///
- /// Used to add/update preview xml for the content item
- ///
- ///
- ///
- void AddOrUpdatePreviewXml(IContent content, Func xml);
-
///
/// Gets paged content results
///
@@ -87,23 +67,9 @@ namespace Umbraco.Core.Persistence.Repositories
/// Direction to order by
/// Flag to indicate when ordering by system field
///
+ /// A value indicating whether to get the 'newest' or all of them.
/// An Enumerable list of objects
IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords,
- string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null);
-
- ///
- /// Returns the persisted content's preview XML structure
- ///
- ///
- ///
- XElement GetContentXml(int contentId);
-
- ///
- /// Returns the persisted content's preview XML structure
- ///
- ///
- ///
- ///
- XElement GetContentPreviewXml(int contentId, Guid version);
+ string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter = null, bool newest = true);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
index 2e165a6ed6..e42827a350 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
@@ -11,20 +11,6 @@ namespace Umbraco.Core.Persistence.Repositories
{
IMedia GetMediaByPath(string mediaPath);
- ///
- /// Used to add/update published xml for the media item
- ///
- ///
- ///
- void AddOrUpdateContentXml(IMedia content, Func xml);
-
- ///
- /// Used to add/update preview xml for the content item
- ///
- ///
- ///
- void AddOrUpdatePreviewXml(IMedia content, Func xml);
-
///
/// Gets paged media results
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
index a24116f0e2..2cab1dc69c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs
@@ -59,20 +59,5 @@ namespace Umbraco.Core.Persistence.Repositories
//IEnumerable GetPagedResultsByQuery(
// Sql sql, int pageIndex, int pageSize, out int totalRecords,
// Func, int[]> resolveIds);
-
- ///
- /// Used to add/update published xml for the media item
- ///
- ///
- ///
- void AddOrUpdateContentXml(IMember content, Func xml);
-
- ///
- /// Used to add/update preview xml for the content item
- ///
- ///
- ///
- void AddOrUpdatePreviewXml(IMember content, Func xml);
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs
index a6ed95711e..ce6ed2561a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRecycleBinRepository.cs
@@ -12,12 +12,5 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
IEnumerable GetEntitiesInRecycleBin();
-
- ///
- /// Called to empty the recycle bin
- ///
- ///
- bool EmptyRecycleBin();
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs
index 229a6fc0ef..6aa9336377 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepositoryVersionable.cs
@@ -13,15 +13,6 @@ namespace Umbraco.Core.Persistence.Repositories
public interface IRepositoryVersionable : IRepositoryQueryable
where TEntity : IAggregateRoot
{
- ///
- /// Rebuilds the xml structures for all TEntity if no content type ids are specified, otherwise rebuilds the xml structures
- /// for only the content types specified
- ///
- /// The serializer to convert TEntity to Xml
- /// Structures will be rebuilt in chunks of this size
- ///
- void RebuildXmlStructures(Func serializer, int groupSize = 5000, IEnumerable contentTypeIds = null);
-
///
/// Get the total count of entities
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
index b97283542e..1fac280f4e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
@@ -24,12 +24,10 @@ namespace Umbraco.Core.Persistence.Repositories
///
/// Represents a repository for doing CRUD operations for
///
- internal class MediaRepository : RecycleBinRepository, IMediaRepository
+ internal class MediaRepository : RecycleBinRepository, IMediaRepository
{
private readonly IMediaTypeRepository _mediaTypeRepository;
private readonly ITagRepository _tagRepository;
- private readonly ContentXmlRepository _contentXmlRepository;
- private readonly ContentPreviewRepository _contentPreviewRepository;
public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection, IMappingResolver mappingResolver)
: base(work, cache, logger, contentSection, mappingResolver)
@@ -38,11 +36,11 @@ namespace Umbraco.Core.Persistence.Repositories
if (tagRepository == null) throw new ArgumentNullException(nameof(tagRepository));
_mediaTypeRepository = mediaTypeRepository;
_tagRepository = tagRepository;
- _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, mappingResolver);
- _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, mappingResolver);
EnsureUniqueNaming = contentSection.EnsureUniqueNaming;
}
+ protected override MediaRepository Instance => this;
+
public bool EnsureUniqueNaming { get; }
#region Overrides of RepositoryBase
@@ -126,9 +124,7 @@ namespace Umbraco.Core.Persistence.Repositories
"DELETE FROM cmsTagRelationship WHERE nodeId = @Id",
"DELETE FROM cmsDocument WHERE nodeId = @Id",
"DELETE FROM cmsPropertyData WHERE contentNodeId = @Id",
- "DELETE FROM cmsPreviewXml WHERE nodeId = @Id",
"DELETE FROM cmsContentVersion WHERE ContentId = @Id",
- "DELETE FROM cmsContentXml WHERE nodeId = @Id",
"DELETE FROM cmsContent WHERE nodeId = @Id",
"DELETE FROM umbracoNode WHERE id = @Id"
};
@@ -204,7 +200,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected override void PerformDeleteVersion(int id, Guid versionId)
{
- Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
+ // raise event first else potential FK issues
+ OnUowRemovingVersion(new UnitOfWorkVersionEventArgs(UnitOfWork, id, versionId));
+
Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId });
}
@@ -282,6 +280,8 @@ namespace Umbraco.Core.Persistence.Repositories
UpdateEntityTags(entity, _tagRepository);
+ OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity));
+
entity.ResetDirtyProperties();
}
@@ -364,9 +364,18 @@ namespace Umbraco.Core.Persistence.Repositories
UpdateEntityTags(entity, _tagRepository);
+ OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity));
+
entity.ResetDirtyProperties();
}
+ protected override void PersistDeletedItem(IMedia entity)
+ {
+ // raise event first else potential FK issues
+ OnUowRemovingEntity(new UnitOfWorkEntityEventArgs(this.UnitOfWork, entity));
+ base.PersistDeletedItem(entity);
+ }
+
#endregion
#region IRecycleBinRepository members
@@ -520,103 +529,5 @@ 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 mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media);
- var subQuery = Sql()
- .Select("DISTINCT cmsContentXml.nodeId")
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .Where(dto => dto.NodeObjectType == mediaObjectType);
-
- var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
- Database.Execute(deleteSql);
- }
- else
- {
- foreach (var id in contentTypeIds)
- {
- var id1 = id;
- var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media);
- var subQuery = Sql()
- .Select("DISTINCT cmsContentXml.nodeId")
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .Where(dto => dto.NodeObjectType == mediaObjectType)
- .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)
- {
- RebuildXmlStructuresProcessQuery(serializer, Query, tr, groupSize);
- }
- else
- {
- foreach (var contentTypeId in contentTypeIds)
- {
- //copy local
- var id = contentTypeId;
- var query = Query.Where(x => 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
- {
- var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "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 void AddOrUpdateContentXml(IMedia content, Func xml)
- {
- _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml));
- }
-
- public void AddOrUpdatePreviewXml(IMedia content, Func xml)
- {
- _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml));
- }
-
- #endregion
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
index 124832b08e..c0b5d1bf83 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
@@ -23,13 +23,11 @@ namespace Umbraco.Core.Persistence.Repositories
///
/// Represents a repository for doing CRUD operations for
///
- internal class MemberRepository : VersionableRepositoryBase, IMemberRepository
+ internal class MemberRepository : VersionableRepositoryBase, IMemberRepository
{
private readonly IMemberTypeRepository _memberTypeRepository;
private readonly ITagRepository _tagRepository;
private readonly IMemberGroupRepository _memberGroupRepository;
- private readonly ContentXmlRepository _contentXmlRepository;
- private readonly ContentPreviewRepository _contentPreviewRepository;
public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection, IMappingResolver mappingResolver)
: base(work, cache, logger, contentSection, mappingResolver)
@@ -39,10 +37,10 @@ namespace Umbraco.Core.Persistence.Repositories
_memberTypeRepository = memberTypeRepository;
_tagRepository = tagRepository;
_memberGroupRepository = memberGroupRepository;
- _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, mappingResolver);
- _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, mappingResolver);
}
+ protected override MemberRepository Instance => this;
+
#region Overrides of RepositoryBase
protected override IMember PerformGet(int id)
@@ -260,6 +258,8 @@ namespace Umbraco.Core.Persistence.Repositories
UpdateEntityTags(entity, _tagRepository);
+ OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity));
+
((Member)entity).ResetDirtyProperties();
}
@@ -373,9 +373,18 @@ namespace Umbraco.Core.Persistence.Repositories
UpdateEntityTags(entity, _tagRepository);
+ OnUowRefreshedEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity));
+
dirtyEntity.ResetDirtyProperties();
}
+ protected override void PersistDeletedItem(IMember entity)
+ {
+ // raise event first else potential FK issues
+ OnUowRemovingEntity(new UnitOfWorkEntityEventArgs(UnitOfWork, entity));
+ base.PersistDeletedItem(entity);
+ }
+
#endregion
#region Overrides of VersionableRepositoryBase
@@ -409,7 +418,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected override void PerformDeleteVersion(int id, Guid versionId)
{
- Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
+ // raise event first else potential FK issues
+ OnUowRemovingVersion(new UnitOfWorkVersionEventArgs(UnitOfWork, id, versionId));
+
Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId });
}
@@ -636,103 +647,5 @@ namespace Umbraco.Core.Persistence.Repositories
((Entity)member).ResetDirtyProperties(false);
return member;
}
-
- #region Xml - Should Move!
-
- public void AddOrUpdateContentXml(IMember content, Func xml)
- {
- _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml));
- }
-
- public void AddOrUpdatePreviewXml(IMember content, Func xml)
- {
- _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(content, xml));
- }
-
- 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 memberObjectType = Guid.Parse(Constants.ObjectTypes.Member);
- var subQuery = Sql()
- .Select("DISTINCT cmsContentXml.nodeId")
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .Where(dto => dto.NodeObjectType == memberObjectType);
-
- var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery);
- Database.Execute(deleteSql);
- }
- else
- {
- foreach (var id in contentTypeIds)
- {
- var id1 = id;
- var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member);
- var subQuery = Sql()
- .Select("DISTINCT cmsContentXml.nodeId")
- .From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .Where(dto => dto.NodeObjectType == memberObjectType)
- .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;
- RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize);
- }
- else
- {
- foreach (var contentTypeId in contentTypeIds)
- {
- //copy local
- var id = contentTypeId;
- var query = Query.Where(x => 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
- {
- var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "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);
- }
-
- #endregion
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs
index 0be15ede71..298fa915d6 100644
--- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs
@@ -10,8 +10,9 @@ using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
- internal abstract class RecycleBinRepository : VersionableRepositoryBase, IRecycleBinRepository
+ internal abstract class RecycleBinRepository : VersionableRepositoryBase, IRecycleBinRepository
where TEntity : class, IUmbracoEntity
+ where TRepository : class, IRepository
{
protected RecycleBinRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, IContentSection contentSection, IMappingResolver mappingResolver)
: base(work, cache, logger, contentSection, mappingResolver)
@@ -25,74 +26,6 @@ namespace Umbraco.Core.Persistence.Repositories
return GetByQuery(Query.Where(entity => entity.Trashed));
}
- ///
- /// Empties the Recycle Bin by running single bulk-Delete queries
- /// against the Content- or Media's Recycle Bin.
- ///
- ///
- public virtual bool EmptyRecycleBin()
- {
- var db = this.Database;
-
- //Construct and execute delete statements for all trashed items by 'nodeObjectType'
- var deletes = new List
- {
- FormatDeleteStatement("umbracoUser2NodeNotify", "nodeId"),
- FormatDeleteStatement("umbracoUser2NodePermission", "nodeId"),
- @"DELETE FROM umbracoAccessRule WHERE umbracoAccessRule.accessId IN (
- SELECT TB1.id FROM umbracoAccess as TB1
- INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id
- WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
- FormatDeleteStatement("umbracoAccess", "nodeId"),
- FormatDeleteStatement("umbracoRelation", "parentId"),
- FormatDeleteStatement("umbracoRelation", "childId"),
- FormatDeleteStatement("cmsTagRelationship", "nodeId"),
- FormatDeleteStatement("umbracoDomains", "domainRootStructureID"),
- FormatDeleteStatement("cmsDocument", "nodeId"),
- FormatDeleteStatement("cmsPropertyData", "contentNodeId"),
- FormatDeleteStatement("cmsPreviewXml", "nodeId"),
- FormatDeleteStatement("cmsContentVersion", "ContentId"),
- FormatDeleteStatement("cmsContentXml", "nodeId"),
- FormatDeleteStatement("cmsContent", "nodeId"),
- "UPDATE umbracoNode SET parentID = '" + RecycleBinId + "' WHERE trashed = '1' AND nodeObjectType = @NodeObjectType",
- "DELETE FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType"
- };
-
- //Wraps in transaction - this improves performance and also ensures
- // that if any of the deletions fails that the whole thing is rolled back.
- using (var trans = db.GetTransaction())
- {
- try
- {
- foreach (var delete in deletes)
- {
- db.Execute(delete, new { NodeObjectType = NodeObjectTypeId });
- }
-
- trans.Complete();
-
- return true;
- }
- catch (Exception ex)
- {
- // transaction will rollback
- Logger.Error>("An error occurred while emptying the Recycle Bin: " + ex.Message, ex);
- throw;
- }
- }
- }
-
- private string FormatDeleteStatement(string tableName, string keyName)
- {
- //This query works with sql ce and sql server:
- //DELETE FROM umbracoUser2NodeNotify WHERE umbracoUser2NodeNotify.nodeId IN
- //(SELECT nodeId FROM umbracoUser2NodeNotify as TB1 INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = 'C66BA18E-EAF3-4CFF-8A22-41B16D66A972')
- return
- string.Format(
- "DELETE FROM {0} WHERE {0}.{1} IN (SELECT TB1.{1} FROM {0} as TB1 INNER JOIN umbracoNode as TB2 ON TB1.{1} = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
- tableName, keyName);
- }
-
///
/// Gets a list of files, which are referenced on items in the Recycle Bin.
/// The list is generated by the convention that a file is referenced by
diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
index 1036b46d13..45a9249d6a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs
@@ -1,14 +1,12 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
-using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NPoco;
using Umbraco.Core.Cache;
-using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
+using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
@@ -22,7 +20,6 @@ using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
-using Umbraco.Core.Dynamics;
using Umbraco.Core.IO;
using Umbraco.Core.Persistence.Mappers;
@@ -48,8 +45,9 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
- internal abstract class VersionableRepositoryBase : NPocoRepositoryBase
+ internal abstract class VersionableRepositoryBase : NPocoRepositoryBase
where TEntity : class, IAggregateRoot
+ where TRepository : class, IRepository
{
private readonly IContentSection _contentSection;
@@ -59,6 +57,8 @@ namespace Umbraco.Core.Persistence.Repositories
_contentSection = contentSection;
}
+ protected abstract TRepository Instance { get; }
+
#region IRepositoryVersionable Implementation
public virtual IEnumerable GetAllVersions(int id)
@@ -103,10 +103,10 @@ namespace Umbraco.Core.Persistence.Repositories
var list =
Database.Fetch(
"WHERE versionId <> @VersionId AND (ContentId = @Id AND VersionDate < @VersionDate)",
- new {VersionId = latestVersionDto.VersionId, Id = id, VersionDate = versionDate});
+ new { /*VersionId =*/ latestVersionDto.VersionId, Id = id, VersionDate = versionDate});
if (list.Any() == false) return;
- using (var transaction = Database.GetTransaction())
+ using (var transaction = Database.GetTransaction()) // fixme - though... already in a unit of work?
{
foreach (var dto in list)
{
@@ -479,7 +479,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (result.ContainsKey(def.Id))
{
- Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name);
+ Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name);
}
result[def.Id] = new PropertyCollection(properties);
}
@@ -541,7 +541,60 @@ namespace Umbraco.Core.Persistence.Repositories
return SqlSyntax.GetQuotedTableName(tableName) + "." + SqlSyntax.GetQuotedColumnName(fieldName);
}
+ #region UnitOfWork Events
+
+ public class UnitOfWorkEntityEventArgs : EventArgs
+ {
+ public UnitOfWorkEntityEventArgs(IDatabaseUnitOfWork unitOfWork, TEntity entity)
+ {
+ UnitOfWork = unitOfWork;
+ Entity = entity;
+ }
+
+ public IDatabaseUnitOfWork UnitOfWork { get; }
+
+ public TEntity Entity { get; }
+ }
+
+ public class UnitOfWorkVersionEventArgs : EventArgs
+ {
+ public UnitOfWorkVersionEventArgs(IDatabaseUnitOfWork unitOfWork, int entityId, Guid versionId)
+ {
+ UnitOfWork = unitOfWork;
+ EntityId = entityId;
+ VersionId = versionId;
+ }
+
+ public IDatabaseUnitOfWork UnitOfWork { get; private set; }
+
+ public int EntityId { get; }
+
+ public Guid VersionId { get; }
+ }
+
+ public static event TypedEventHandler UowRefreshedEntity;
+ public static event TypedEventHandler UowRemovingEntity;
+ public static event TypedEventHandler UowRemovingVersion;
+
+ protected void OnUowRefreshedEntity(UnitOfWorkEntityEventArgs args)
+ {
+ UowRefreshedEntity.RaiseEvent(args, Instance);
+ }
+
+ protected void OnUowRemovingEntity(UnitOfWorkEntityEventArgs args)
+ {
+ UowRemovingEntity.RaiseEvent(args, Instance);
+ }
+
+ protected void OnUowRemovingVersion(UnitOfWorkVersionEventArgs args)
+ {
+ UowRemovingVersion.RaiseEvent(args, Instance);
+ }
+
+ #endregion
+
///
+ ///
/// Deletes all media files passed in.
///
///
@@ -578,7 +631,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
catch (Exception e)
{
- Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e);
+ Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e);
allsuccess = false;
}
});
diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs
new file mode 100644
index 0000000000..a5182f0829
--- /dev/null
+++ b/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs
@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.Services.Changes
+{
+ internal class ContentTypeChange
+ where TItem : class, IContentTypeComposition
+ {
+ public ContentTypeChange(TItem item, ContentTypeChangeTypes changeTypes)
+ {
+ Item = item;
+ ChangeTypes = changeTypes;
+ }
+
+ public TItem Item { get; }
+
+ public ContentTypeChangeTypes ChangeTypes { get; internal set; }
+
+ public EventArgs ToEventArgs(ContentTypeChange change)
+ {
+ return new EventArgs(change);
+ }
+
+ public class EventArgs : System.EventArgs
+ {
+ public EventArgs(IEnumerable> changes)
+ {
+ Changes = changes.ToArray();
+ }
+
+ public EventArgs(ContentTypeChange change)
+ : this(new[] { change })
+ { }
+
+ public IEnumerable> Changes { get; private set; }
+ }
+ }
+
+}
diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs
new file mode 100644
index 0000000000..f783098db0
--- /dev/null
+++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.Services.Changes
+{
+ internal static class ContentTypeChangeExtensions
+ {
+ public static ContentTypeChange.EventArgs ToEventArgs(this IEnumerable> changes)
+ where TItem : class, IContentTypeComposition
+ {
+ return new ContentTypeChange.EventArgs(changes);
+ }
+
+ public static bool HasType(this ContentTypeChangeTypes change, ContentTypeChangeTypes type)
+ {
+ return (change & type) != ContentTypeChangeTypes.None;
+ }
+
+ public static bool HasTypesAll(this ContentTypeChangeTypes change, ContentTypeChangeTypes types)
+ {
+ return (change & types) == types;
+ }
+
+ public static bool HasTypesAny(this ContentTypeChangeTypes change, ContentTypeChangeTypes types)
+ {
+ return (change & types) != ContentTypeChangeTypes.None;
+ }
+
+ public static bool HasTypesNone(this ContentTypeChangeTypes change, ContentTypeChangeTypes types)
+ {
+ return (change & types) == ContentTypeChangeTypes.None;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs
new file mode 100644
index 0000000000..a39cd4617b
--- /dev/null
+++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Umbraco.Core.Services.Changes
+{
+ [Flags]
+ public enum ContentTypeChangeTypes : byte
+ {
+ None = 0,
+ RefreshMain = 1, // changed, impacts content (adding ppty or composition does NOT)
+ RefreshOther = 2, // changed, other changes
+ Remove = 4 // item type has been removed
+ }
+}
diff --git a/src/Umbraco.Core/Services/Changes/TreeChange.cs b/src/Umbraco.Core/Services/Changes/TreeChange.cs
new file mode 100644
index 0000000000..81c9b67c3f
--- /dev/null
+++ b/src/Umbraco.Core/Services/Changes/TreeChange.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Umbraco.Core.Services.Changes
+{
+ internal class TreeChange
+ {
+ public TreeChange(TItem changedItem, TreeChangeTypes changeTypes)
+ {
+ Item = changedItem;
+ ChangeTypes = changeTypes;
+ }
+
+ public TItem Item { get; }
+ public TreeChangeTypes ChangeTypes { get; }
+
+ public EventArgs ToEventArgs()
+ {
+ return new EventArgs(this);
+ }
+
+ public class EventArgs : System.EventArgs
+ {
+ public EventArgs(IEnumerable> changes)
+ {
+ Changes = changes.ToArray();
+ }
+
+ public EventArgs(TreeChange change)
+ : this(new[] { change })
+ { }
+
+ public IEnumerable> Changes { get; private set; }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs
new file mode 100644
index 0000000000..e8c1db3f44
--- /dev/null
+++ b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Services.Changes
+{
+ internal static class TreeChangeExtensions
+ {
+ public static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes)
+ {
+ return new TreeChange.EventArgs(changes);
+ }
+
+ public static bool HasType(this TreeChangeTypes change, TreeChangeTypes type)
+ {
+ return (change & type) != TreeChangeTypes.None;
+ }
+
+ public static bool HasTypesAll(this TreeChangeTypes change, TreeChangeTypes types)
+ {
+ return (change & types) == types;
+ }
+
+ public static bool HasTypesAny(this TreeChangeTypes change, TreeChangeTypes types)
+ {
+ return (change & types) != TreeChangeTypes.None;
+ }
+
+ public static bool HasTypesNone(this TreeChangeTypes change, TreeChangeTypes types)
+ {
+ return (change & types) == TreeChangeTypes.None;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs
new file mode 100644
index 0000000000..34d5f39b65
--- /dev/null
+++ b/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Umbraco.Core.Services.Changes
+{
+ [Flags]
+ public enum TreeChangeTypes : byte
+ {
+ None = 0,
+
+ // all items have been refreshed
+ RefreshAll = 1,
+
+ // an item node has been refreshed
+ // with only local impact
+ RefreshNode = 2,
+
+ // an item node has been refreshed
+ // with branch impact
+ RefreshBranch = 4,
+
+ // an item node has been removed
+ // never to return
+ Remove = 8,
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index c2f803e945..22b34dcdb5 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -12,6 +12,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
+using Umbraco.Core.Services.Changes;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Services
@@ -21,10 +22,6 @@ namespace Umbraco.Core.Services
///
public class ContentService : RepositoryService, IContentService, IContentServiceOperations
{
- private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer();
- private readonly IDataTypeService _dataTypeService;
- private readonly IUserService _userService;
- private readonly IEnumerable _urlSegmentProviders;
private IContentTypeService _contentTypeService;
#region Constructors
@@ -32,18 +29,9 @@ namespace Umbraco.Core.Services
public ContentService(
IDatabaseUnitOfWorkProvider provider,
ILogger logger,
- IEventMessagesFactory eventMessagesFactory,
- IDataTypeService dataTypeService,
- IUserService userService,
- IEnumerable urlSegmentProviders)
+ IEventMessagesFactory eventMessagesFactory)
: base(provider, logger, eventMessagesFactory)
{
- if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService));
- if (userService == null) throw new ArgumentNullException(nameof(userService));
- if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders));
- _dataTypeService = dataTypeService;
- _userService = userService;
- _urlSegmentProviders = urlSegmentProviders;
}
// don't change or remove this, will need it later
@@ -343,9 +331,9 @@ namespace Umbraco.Core.Services
var repo = uow.CreateRepository();
repo.AddOrUpdate(content);
- repo.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
Saved.RaiseEvent(new SaveEventArgs(content, false), this);
+ TreeChanged.RaiseEvent(new TreeChange(content, TreeChangeTypes.RefreshNode).ToEventArgs(), this);
}
Created.RaiseEvent(new NewEventArgs(content, false, content.ContentType.Alias, parent), this);
@@ -591,7 +579,7 @@ namespace Umbraco.Core.Services
{
uow.ReadLock(Constants.Locks.ContentTree);
var repository = uow.CreateRepository();
- var filterQuery = filter.IsNullOrWhiteSpace()
+ var filterQuery = filter.IsNullOrWhiteSpace()
? null
: repository.QueryFactory.Create().Where(x => x.Name.Contains(filter));
return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filterQuery);
@@ -964,48 +952,6 @@ namespace Umbraco.Core.Services
#region Save, Publish, Unpublish
- ///
- /// This will rebuild the xml structures for content in the database.
- ///
- /// This is not used for anything
- /// True if publishing succeeded, otherwise False
- ///
- /// This is used for when a document type alias or a document type property is changed, the xml will need to
- /// be regenerated.
- ///
- public bool RePublishAll(int userId = 0)
- {
- try
- {
- RebuildXmlStructures();
- return true;
- }
- catch (Exception ex)
- {
- Logger.Error("An error occurred executing RePublishAll", ex);
- return false;
- }
- }
-
- ///
- /// This will rebuild the xml structures for content in the database.
- ///
- ///
- /// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure
- /// for all published content.
- ///
- internal void RePublishAll(params int[] contentTypeIds)
- {
- try
- {
- RebuildXmlStructures(contentTypeIds);
- }
- catch (Exception ex)
- {
- Logger.Error("An error occurred executing RePublishAll", ex);
- }
- }
-
///
/// Saves a single object
///
@@ -1030,6 +976,8 @@ namespace Umbraco.Core.Services
if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(content, evtMsgs), this))
return OperationStatus.Attempt.Cancel(evtMsgs);
+ var isNew = content.IsNewEntity();
+
using (var uow = UowProvider.CreateUnitOfWork())
{
uow.WriteLock(Constants.Locks.ContentTree);
@@ -1045,14 +993,14 @@ namespace Umbraco.Core.Services
content.ChangePublishedState(PublishedState.Saving);
repository.AddOrUpdate(content);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
uow.Complete();
}
if (raiseEvents)
Saved.RaiseEvent(new SaveEventArgs(content, false, evtMsgs), this);
-
+ var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode;
+ TreeChanged.RaiseEvent(new TreeChange(content, changeType).ToEventArgs(), this);
Audit(AuditType.Save, "Save Content performed by user", userId, content.Id);
return OperationStatus.Attempt.Succeed(evtMsgs);
@@ -1087,6 +1035,9 @@ namespace Umbraco.Core.Services
if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(contentsA, evtMsgs), this))
return OperationStatus.Attempt.Cancel(evtMsgs);
+ var treeChanges = contentsA.Select(x => new TreeChange(x,
+ x.IsNewEntity() ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode));
+
using (var uow = UowProvider.CreateUnitOfWork())
{
uow.WriteLock(Constants.Locks.ContentTree);
@@ -1103,7 +1054,6 @@ namespace Umbraco.Core.Services
content.ChangePublishedState(PublishedState.Saving);
repository.AddOrUpdate(content);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
}
uow.Complete();
@@ -1111,6 +1061,7 @@ namespace Umbraco.Core.Services
if (raiseEvents)
Saved.RaiseEvent(new SaveEventArgs(contentsA, false, evtMsgs), this);
+ TreeChanged.RaiseEvent(treeChanges.ToEventArgs(), this);
Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root);
return OperationStatus.Attempt.Succeed(evtMsgs);
@@ -1348,6 +1299,7 @@ namespace Umbraco.Core.Services
DeleteLocked(repository, content);
uow.Complete();
+ TreeChanged.RaiseEvent(new TreeChange(content, TreeChangeTypes.Remove).ToEventArgs(), this);
}
Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id);
@@ -1491,6 +1443,7 @@ namespace Umbraco.Core.Services
PerformMoveLocked(repository, content, Constants.System.RecycleBinContent, null, userId, moves, true);
uow.Complete();
+ TreeChanged.RaiseEvent(new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs(), this);
}
var moveInfo = moves
@@ -1555,6 +1508,7 @@ namespace Umbraco.Core.Services
PerformMoveLocked(repository, content, parentId, parent, userId, moves, trashed);
uow.Complete();
+ TreeChanged.RaiseEvent(new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs(), this);
}
var moveInfo = moves //changes
@@ -1649,6 +1603,7 @@ namespace Umbraco.Core.Services
EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, true), this);
uow.Complete();
+ TreeChanged.RaiseEvent(deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs(), this);
}
Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent);
@@ -1707,7 +1662,6 @@ namespace Umbraco.Core.Services
// save
repository.AddOrUpdate(copy);
- repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
uow.Flush(); // ensure copy has an ID - fixme why?
@@ -1729,7 +1683,6 @@ namespace Umbraco.Core.Services
dcopy.WriterId = userId;
repository.AddOrUpdate(dcopy);
- repository.AddOrUpdatePreviewXml(dcopy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
copyIds[descendant.Id] = dcopy;
}
@@ -1743,6 +1696,7 @@ namespace Umbraco.Core.Services
uow.Complete();
}
+ TreeChanged.RaiseEvent(new TreeChange(copy, TreeChangeTypes.RefreshBranch).ToEventArgs(), this);
Copied.RaiseEvent(new CopyEventArgs(content, copy, false, parentId, relateToOriginal), this);
Audit(AuditType.Copy, "Copy Content performed by user", content.WriterId, content.Id);
return copy;
@@ -1805,11 +1759,12 @@ namespace Umbraco.Core.Services
content.ChangePublishedState(PublishedState.Saving);
repository.AddOrUpdate(content);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
uow.Complete();
}
RolledBack.RaiseEvent(new RollbackEventArgs(content, false), this);
+ TreeChanged.RaiseEvent(new TreeChange(content, TreeChangeTypes.RefreshNode).ToEventArgs(), this);
+
Audit(AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id);
return content;
@@ -1868,12 +1823,8 @@ namespace Umbraco.Core.Services
// save
saved.Add(content);
repository.AddOrUpdate(content);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
}
- foreach (var content in published)
- repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
-
uow.Complete();
}
@@ -1883,6 +1834,8 @@ namespace Umbraco.Core.Services
if (raiseEvents && published.Any())
Published.RaiseEvent(new PublishEventArgs(published, false, false), this);
+ TreeChanged.RaiseEvent(saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs(), this);
+
Audit(AuditType.Sort, "Sorting content performed by user", userId, 0);
return true;
@@ -1992,8 +1945,6 @@ namespace Umbraco.Core.Services
var publishedItem = status.ContentItem;
publishedItem.WriterId = userId;
repository.AddOrUpdate(publishedItem);
- repository.AddOrUpdatePreviewXml(publishedItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
- repository.AddOrUpdateContentXml(publishedItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
publishedItems.Add(publishedItem);
}
@@ -2001,6 +1952,7 @@ namespace Umbraco.Core.Services
}
Published.RaiseEvent(new PublishEventArgs(publishedItems, false, false), this);
+ TreeChanged.RaiseEvent(new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs(), this);
Audit(AuditType.Publish, "Publish with Children performed by user", userId, content.Id);
return attempts;
}
@@ -2041,13 +1993,13 @@ namespace Umbraco.Core.Services
content.WriterId = userId;
repository.AddOrUpdate(content);
- // fixme delete xml from database! was in _publishingStrategy.UnPublishingFinalized(content);
- repository.DeleteContentXml(content);
uow.Complete();
}
UnPublished.RaiseEvent(new PublishEventArgs(content, false, false), this);
+ TreeChanged.RaiseEvent(new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs(), this);
+ Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id);
return Attempt.Succeed(new UnPublishStatus(UnPublishedStatusType.Success, evtMsgs, content));
}
@@ -2066,8 +2018,9 @@ namespace Umbraco.Core.Services
return Attempt.Fail(new PublishStatus(PublishStatusType.FailedCancelledByEvent, evtMsgs, content));
var isNew = content.IsNewEntity();
+ var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode;
var previouslyPublished = content.HasIdentity && content.HasPublishedVersion;
- var status = default(Attempt);
+ Attempt status;
using (var uow = UowProvider.CreateUnitOfWork())
{
@@ -2094,9 +2047,6 @@ namespace Umbraco.Core.Services
content.WriterId = userId;
repository.AddOrUpdate(content);
- repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
- if (content.Published)
- repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
uow.Complete();
}
@@ -2104,6 +2054,7 @@ namespace Umbraco.Core.Services
if (status.Success == false)
{
// fixme what about the saved event?
+ TreeChanged.RaiseEvent(new TreeChange(content, changeType).ToEventArgs(), this);
return status;
}
@@ -2121,6 +2072,9 @@ namespace Umbraco.Core.Services
}
}
+ // invalidate the node/branch
+ TreeChanged.RaiseEvent(new TreeChange(content, changeType).ToEventArgs(), this);
+
Audit(AuditType.Publish, "Save and Publish performed by user", userId, content.Id);
return status;
}
@@ -2298,6 +2252,11 @@ namespace Umbraco.Core.Services
///
public static event TypedEventHandler> UnPublished;
+ ///
+ /// Occurs after change.
+ ///
+ internal static event TypedEventHandler.EventArgs> TreeChanged;
+
#endregion
#region Publishing Strategies
@@ -2524,6 +2483,7 @@ namespace Umbraco.Core.Services
// The main problem with this is that for every content item being deleted, events are raised...
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
+ var changes = new List>();
var moves = new List>();
using (var uow = UowProvider.CreateUnitOfWork())
@@ -2556,11 +2516,13 @@ namespace Umbraco.Core.Services
{
// see MoveToRecycleBin
PerformMoveLocked(repository, child, Constants.System.RecycleBinContent, null, userId, moves, true);
+ changes.Add(new TreeChange(content, TreeChangeTypes.RefreshBranch));
}
// delete content
// triggers the deleted event (and handles the files)
DeleteLocked(repository, content);
+ changes.Add(new TreeChange(content, TreeChangeTypes.Remove));
}
uow.Complete();
@@ -2571,6 +2533,7 @@ namespace Umbraco.Core.Services
.ToArray();
if (moveInfos.Length > 0)
Trashed.RaiseEvent(new MoveEventArgs(false, moveInfos), this);
+ TreeChanged.RaiseEvent(changes.ToEventArgs(), this);
Audit(AuditType.Delete, $"Delete Content of Type {contentTypeId} performed by user", userId, Constants.System.Root);
}
@@ -2596,66 +2559,5 @@ namespace Umbraco.Core.Services
}
#endregion
-
- #region Xml - Should Move!
-
- ///
- /// Returns the persisted content's XML structure
- ///
- ///
- ///
- public XElement GetContentXml(int contentId)
- {
- using (var uow = UowProvider.CreateUnitOfWork())
- {
- uow.ReadLock(Constants.Locks.ContentTree);
- var repository = uow.CreateRepository();
- var elt = repository.GetContentXml(contentId);
- uow.Complete();
- return elt;
- }
- }
-
- ///
- /// Returns the persisted content's preview XML structure
- ///
- ///
- ///
- ///
- public XElement GetContentPreviewXml(int contentId, Guid version)
- {
- using (var uow = UowProvider.CreateUnitOfWork())
- {
- uow.ReadLock(Constants.Locks.ContentTree);
- var repository = uow.CreateRepository();
- var elt = repository.GetContentPreviewXml(contentId, version);
- uow.Complete();
- return elt;
- }
- }
-
- ///
- /// Rebuilds all xml content in the cmsContentXml table for all documents
- ///
- ///
- /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures
- /// for all content
- ///
- public void RebuildXmlStructures(params int[] contentTypeIds)
- {
- using (var uow = UowProvider.CreateUnitOfWork())
- {
- uow.WriteLock(Constants.Locks.ContentTree);
- var repository = uow.CreateRepository();
- repository.RebuildXmlStructures(
- content => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, content),
- contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds);
- uow.Complete();
- }
-
- Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root);
- }
-
- #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs
index fcbad641cf..1c0d38410f 100644
--- a/src/Umbraco.Core/Services/ContentTypeService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeService.cs
@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using Umbraco.Core.Events;
-using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
-using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
@@ -25,6 +21,7 @@ namespace Umbraco.Core.Services
_contentService = contentService;
}
+ protected override IContentTypeService Instance => this;
// beware! order is important to avoid deadlocks
protected override int[] ReadLockIds { get; } = { Constants.Locks.ContentTypes };
@@ -88,69 +85,5 @@ namespace Umbraco.Core.Services
return aliases;
}
}
-
- ///
- /// Generates the complete (simplified) XML DTD.
- ///
- /// The DTD as a string
- public string GetDtd()
- {
- var dtd = new StringBuilder();
- dtd.AppendLine("");
-
- return dtd.ToString();
- }
-
- ///
- /// Generates the complete XML DTD without the root.
- ///
- /// The DTD as a string
- public string GetContentTypesDtd()
- {
- var dtd = new StringBuilder();
- try
- {
- var strictSchemaBuilder = new StringBuilder();
-
- var contentTypes = GetAll(new int[0]);
- foreach (ContentType contentType in contentTypes)
- {
- string safeAlias = contentType.Alias.ToSafeAlias();
- if (safeAlias != null)
- {
- strictSchemaBuilder.AppendLine($"");
- strictSchemaBuilder.AppendLine($"");
- }
- }
-
- // Only commit the strong schema to the container if we didn't generate an error building it
- dtd.Append(strictSchemaBuilder);
- }
- catch (Exception exception)
- {
- LogHelper.Error("Error while trying to build DTD for Xml schema; is Umbraco installed correctly and the connection string configured?", exception);
- }
- return dtd.ToString();
- }
-
- protected override void UpdateContentXmlStructure(params IContentTypeBase[] contentTypes)
- {
- var toUpdate = GetContentTypesForXmlUpdates(contentTypes).ToArray();
- if (toUpdate.Any() == false) return;
-
- var contentService = _contentService as ContentService;
- if (contentService != null)
- {
- contentService.RePublishAll(toUpdate.Select(x => x.Id).ToArray());
- }
- else
- {
- //this should never occur, the content service should always be typed but we'll check anyways.
- _contentService.RePublishAll();
- }
- }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
index 04216f558a..3ed56b5217 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
@@ -9,6 +9,7 @@ using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
+using Umbraco.Core.Services.Changes;
namespace Umbraco.Core.Services
{
@@ -17,87 +18,6 @@ namespace Umbraco.Core.Services
protected ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory)
: base(provider, logger, eventMessagesFactory)
{ }
-
- ///
- /// This is called after an content type is saved and is used to update the content xml structures in the database
- /// if they are required to be updated.
- ///
- ///
- internal IEnumerable GetContentTypesForXmlUpdates(params IContentTypeBase[] contentTypes)
- {
-
- var toUpdate = new List();
-
- foreach (var contentType in contentTypes)
- {
- //we need to determine if we need to refresh the xml content in the database. This is to be done when:
- // - the item is not new (already existed in the db) AND
- // - a content type changes it's alias OR
- // - if a content type has it's property removed OR
- // - if a content type has a property whose alias has changed
- //here we need to check if the alias of the content type changed or if one of the properties was removed.
- var dirty = contentType as IRememberBeingDirty;
- if (dirty == null) continue;
-
- //check if any property types have changed their aliases (and not new property types)
- var hasAnyPropertiesChangedAlias = contentType.PropertyTypes.Any(propType =>
- {
- var dirtyProperty = propType as IRememberBeingDirty;
- if (dirtyProperty == null) return false;
- return dirtyProperty.WasPropertyDirty("HasIdentity") == false //ensure it's not 'new'
- && dirtyProperty.WasPropertyDirty("Alias"); //alias has changed
- });
-
- if (dirty.WasPropertyDirty("HasIdentity") == false //ensure it's not 'new'
- && (dirty.WasPropertyDirty("Alias") || dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved") || hasAnyPropertiesChangedAlias))
- {
- //If the alias was changed then we only need to update the xml structures for content of the current content type.
- //If a property was deleted or a property alias was changed then we need to update the xml structures for any
- // content of the current content type and any of the content type's child content types.
- if (dirty.WasPropertyDirty("Alias")
- && dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved") == false && hasAnyPropertiesChangedAlias == false)
- {
- //if only the alias changed then only update the current content type
- toUpdate.Add(contentType);
- }
- else
- {
- //TODO: This is pretty nasty, fix this
- var contentTypeService = this as IContentTypeService;
- if (contentTypeService != null)
- {
- //if a property was deleted or alias changed, then update all content of the current content type
- // and all of it's desscendant doc types.
- toUpdate.AddRange(((IContentType) contentType).DescendantsAndSelf(contentTypeService));
- }
- else
- {
- var mediaTypeService = this as IMediaTypeService;
- if (mediaTypeService != null)
- {
- //if a property was deleted or alias changed, then update all content of the current content type
- // and all of it's desscendant doc types.
- toUpdate.AddRange(((IMediaType) contentType).DescendantsAndSelf(mediaTypeService));
- }
- else
- {
- var memberTypeService = this as IMemberTypeService;
- if (memberTypeService != null)
- {
- //if a property was deleted or alias changed, then update all content of the current content type
- // and all of it's desscendant doc types.
- toUpdate.AddRange(((IMemberType)contentType).DescendantsAndSelf(memberTypeService));
- }
- }
- }
-
- }
- }
- }
-
- return toUpdate;
-
- }
}
internal abstract class ContentTypeServiceBase : ContentTypeServiceBase
@@ -106,29 +26,40 @@ namespace Umbraco.Core.Services
{
protected ContentTypeServiceBase(IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory)
: base(provider, logger, eventMessagesFactory)
+ { }
+
+ protected abstract TService Instance { get; }
+
+ internal static event TypedEventHandler.EventArgs> Changed;
+
+ protected void OnChanged(ContentTypeChange.EventArgs args)
{
- _this = this as TService;
- if (_this == null) throw new Exception("Oops.");
+ Changed.RaiseEvent(args, Instance);
}
- private readonly TService _this;
+ public static event TypedEventHandler.EventArgs> UowRefreshedEntity;
+
+ protected void OnUowRefreshedEntity(ContentTypeChange.EventArgs args)
+ {
+ UowRefreshedEntity.RaiseEvent(args, Instance);
+ }
public static event TypedEventHandler> Saving;
public static event TypedEventHandler> Saved;
protected void OnSaving(SaveEventArgs args)
{
- Saving.RaiseEvent(args, _this);
+ Saving.RaiseEvent(args, Instance);
}
protected bool OnSavingCancelled(SaveEventArgs args)
{
- return Saving.IsRaisedEventCancelled(args, _this);
+ return Saving.IsRaisedEventCancelled(args, Instance);
}
protected void OnSaved(SaveEventArgs args)
{
- Saved.RaiseEvent(args, _this);
+ Saved.RaiseEvent(args, Instance);
}
public static event TypedEventHandler> Deleting;
@@ -136,12 +67,12 @@ namespace Umbraco.Core.Services
protected void OnDeleting(DeleteEventArgs args)
{
- Deleting.RaiseEvent(args, _this);
+ Deleting.RaiseEvent(args, Instance);
}
protected bool OnDeletingCancelled(DeleteEventArgs args)
{
- return Deleting.IsRaisedEventCancelled(args, _this);
+ return Deleting.IsRaisedEventCancelled(args, Instance);
}
protected void OnDeleted(DeleteEventArgs args)
@@ -154,17 +85,17 @@ namespace Umbraco.Core.Services
protected void OnMoving(MoveEventArgs args)
{
- Moving.RaiseEvent(args, _this);
+ Moving.RaiseEvent(args, Instance);
}
protected bool OnMovingCancelled(MoveEventArgs args)
{
- return Moving.IsRaisedEventCancelled(args, _this);
+ return Moving.IsRaisedEventCancelled(args, Instance);
}
protected void OnMoved(MoveEventArgs args)
{
- Moved.RaiseEvent(args, _this);
+ Moved.RaiseEvent(args, Instance);
}
public static event TypedEventHandler> SavingContainer;
@@ -172,17 +103,17 @@ namespace Umbraco.Core.Services
protected void OnSavingContainer(SaveEventArgs args)
{
- SavingContainer.RaiseEvent(args, _this);
+ SavingContainer.RaiseEvent(args, Instance);
}
protected bool OnSavingContainerCancelled(SaveEventArgs args)
{
- return SavingContainer.IsRaisedEventCancelled(args, _this);
+ return SavingContainer.IsRaisedEventCancelled(args, Instance);
}
protected void OnSavedContainer(SaveEventArgs args)
{
- SavedContainer.RaiseEvent(args, _this);
+ SavedContainer.RaiseEvent(args, Instance);
}
public static event TypedEventHandler> DeletingContainer;
@@ -190,26 +121,18 @@ namespace Umbraco.Core.Services
protected void OnDeletingContainer(DeleteEventArgs args)
{
- DeletingContainer.RaiseEvent(args, _this);
+ DeletingContainer.RaiseEvent(args, Instance);
}
protected bool OnDeletingContainerCancelled(DeleteEventArgs args)
{
- return DeletingContainer.IsRaisedEventCancelled(args, _this);
+ return DeletingContainer.IsRaisedEventCancelled(args, Instance);
}
protected void OnDeletedContainer(DeleteEventArgs args)
{
- DeletedContainer.RaiseEvent(args, _this);
+ DeletedContainer.RaiseEvent(args, Instance);
}
-
- // for later usage
- //public static event TypedEventHandler TxRefreshed;
-
- //protected void OnTxRefreshed(Change.EventArgs args)
- //{
- // TxRefreshed.RaiseEvent(args, this);
- //}
}
internal abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeServiceBase
@@ -297,6 +220,92 @@ namespace Umbraco.Core.Services
#endregion
#region Composition
+
+ internal IEnumerable> ComposeContentTypeChanges(params TItem[] contentTypes)
+ {
+ // find all content types impacted by the changes,
+ // - content type alias changed
+ // - content type property removed, or alias changed
+ // - content type composition removed (not testing if composition had properties...)
+ //
+ // because these are the changes that would impact the raw content data
+
+ // note
+ // this is meant to run *after* uow.Commit() so must use WasPropertyDirty() everywhere
+ // instead of IsPropertyDirty() since dirty properties have been resetted already
+
+ var changes = new List>();
+
+ foreach (var contentType in contentTypes)
+ {
+ var dirty = (IRememberBeingDirty)contentType;
+
+ // skip new content types
+ var isNewContentType = dirty.WasPropertyDirty("HasIdentity");
+ if (isNewContentType)
+ {
+ AddChange(changes, contentType, ContentTypeChangeTypes.RefreshOther);
+ continue;
+ }
+
+ // alias change?
+ var hasAliasChanged = dirty.WasPropertyDirty("Alias");
+
+ // existing property alias change?
+ var hasAnyPropertyChangedAlias = contentType.PropertyTypes.Any(propertyType =>
+ {
+ var dirtyProperty = propertyType as IRememberBeingDirty;
+ if (dirtyProperty == null) throw new Exception("oops");
+
+ // skip new properties
+ var isNewProperty = dirtyProperty.WasPropertyDirty("HasIdentity");
+ if (isNewProperty) return false;
+
+ // alias change?
+ var hasPropertyAliasBeenChanged = dirtyProperty.WasPropertyDirty("Alias");
+ return hasPropertyAliasBeenChanged;
+ });
+
+ // removed properties?
+ var hasAnyPropertyBeenRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved");
+
+ // removed compositions?
+ var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved");
+
+ // main impact on properties?
+ var hasPropertyMainImpact = hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias;
+
+ if (hasAliasChanged || hasPropertyMainImpact)
+ {
+ // add that one, as a main change
+ AddChange(changes, contentType, ContentTypeChangeTypes.RefreshMain);
+
+ if (hasPropertyMainImpact)
+ foreach (var c in contentType.ComposedOf(this))
+ AddChange(changes, c, ContentTypeChangeTypes.RefreshMain);
+ }
+ else
+ {
+ // add that one, as an other change
+ AddChange(changes, contentType, ContentTypeChangeTypes.RefreshOther);
+ }
+ }
+
+ return changes;
+ }
+
+ // ensures changes contains no duplicates
+ private static void AddChange(ICollection> changes, TItem contentType, ContentTypeChangeTypes changeTypes)
+ {
+ var change = changes.FirstOrDefault(x => x.Item == contentType);
+ if (change == null)
+ {
+ changes.Add(new ContentTypeChange(contentType, changeTypes));
+ return;
+ }
+ change.ChangeTypes |= changeTypes;
+ }
+
#endregion
#region Get, Has, Is, Count
@@ -514,15 +523,15 @@ namespace Umbraco.Core.Services
item.CreatorId = userId;
repo.AddOrUpdate(item); // also updates content/media/member items
uow.Flush(); // to db but no commit yet
-
- // ...
-
+
+ // figure out impacted content types
+ var changes = ComposeContentTypeChanges(item).ToArray();
+ var args = changes.ToEventArgs();
+ OnUowRefreshedEntity(args);
uow.Complete();
+ OnChanged(args);
}
- // todo: should use TxRefreshed event within the transaction instead, see CC branch
- UpdateContentXmlStructure(item);
-
OnSaved(new SaveEventArgs(item, false));
Audit(AuditType.Save, $"Save {typeof(TItem).Name} performed by user", userId, item.Id);
}
@@ -552,10 +561,13 @@ namespace Umbraco.Core.Services
//save it all in one go
uow.Complete();
- }
- // todo: should use TxRefreshed event within the transaction instead, see CC branch
- UpdateContentXmlStructure(itemsA.Cast().ToArray());
+ // figure out impacted content types
+ var changes = ComposeContentTypeChanges(itemsA).ToArray();
+ var args = changes.ToEventArgs();
+ OnUowRefreshedEntity(args);
+ OnChanged(args);
+ }
OnSaved(new SaveEventArgs(itemsA, false));
Audit(AuditType.Save, $"Save {typeof(TItem).Name} performed by user", userId, -1);
@@ -579,6 +591,14 @@ namespace Umbraco.Core.Services
var descendantsAndSelf = item.DescendantsAndSelf(this)
.ToArray();
+ // all impacted (through composition) probably lose some properties
+ // don't try to be too clever here, just report them all
+ // do this before anything is deleted
+ var changed = descendantsAndSelf.SelectMany(xx => xx.ComposedOf(this))
+ .Distinct()
+ .Except(descendantsAndSelf)
+ .ToArray();
+
// delete content
DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id));
@@ -592,8 +612,13 @@ namespace Umbraco.Core.Services
uow.Flush(); // to db but no commit yet
//...
+ var changes = descendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove))
+ .Concat(changed.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)));
+ var args = changes.ToEventArgs();
+ OnUowRefreshedEntity(args);
uow.Complete();
+ OnChanged(args);
}
OnDeleted(new DeleteEventArgs(item, false));
@@ -617,6 +642,14 @@ namespace Umbraco.Core.Services
.Distinct()
.ToArray();
+ // all impacted (through composition) probably lose some properties
+ // don't try to be too clever here, just report them all
+ // do this before anything is deleted
+ var changed = allDescendantsAndSelf.SelectMany(x => x.ComposedOf(this))
+ .Distinct()
+ .Except(allDescendantsAndSelf)
+ .ToArray();
+
// delete content
DeleteItemsOfTypes(allDescendantsAndSelf.Select(x => x.Id));
@@ -627,10 +660,15 @@ namespace Umbraco.Core.Services
uow.Flush(); // to db but no commit yet
- // ...
-
+ var changes = allDescendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove))
+ .Concat(changed.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)));
+ var args = changes.ToEventArgs();
+
uow.Complete();
+
+ OnUowRefreshedEntity(args);
+ OnChanged(args);
}
OnDeleted(new DeleteEventArgs(itemsA, false));
@@ -785,6 +823,10 @@ namespace Umbraco.Core.Services
}
}
+
+ // FIXME should raise Changed events for the content type that is MOVED and ALL ITS CHILDREN IF ANY
+ // FIXME at the moment we don't have no MoveContainer don't we?!
+
OnMoved(new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()));
return OperationStatus.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs);
@@ -975,11 +1017,5 @@ namespace Umbraco.Core.Services
}
#endregion
-
- #region Xml - Should Move!
-
- protected abstract void UpdateContentXmlStructure(params IContentTypeBase[] contentTypes);
-
- #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index a5a3f35683..e8d704e686 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -29,10 +29,10 @@ namespace Umbraco.Core.Services
///
/// Saves a collection of objects.
- ///
+ ///
/// Collection of to save
/// Optional Id of the User saving the Content
- /// Optional boolean indicating whether or not to raise events.
+ /// Optional boolean indicating whether or not to raise events.
Attempt Save(IEnumerable contents, int userId = 0, bool raiseEvents = true);
///
@@ -99,32 +99,6 @@ namespace Umbraco.Core.Services
///
public interface IContentService : IService
{
-
-
- ///
- /// Returns the persisted content's XML structure
- ///
- ///
- ///
- XElement GetContentXml(int contentId);
-
- ///
- /// Returns the persisted content's preview XML structure
- ///
- ///
- ///
- ///
- XElement GetContentPreviewXml(int contentId, Guid version);
-
- ///
- /// Rebuilds all xml content in the cmsContentXml table for all documents
- ///
- ///
- /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures
- /// for all content
- ///
- void RebuildXmlStructures(params int[] contentTypeIds);
-
int CountPublished(string contentTypeAlias = null);
int Count(string contentTypeAlias = null);
int CountChildren(int parentId, string contentTypeAlias = null);
@@ -222,7 +196,7 @@ namespace Umbraco.Core.Services
/// Id of the Parent to retrieve Children from
/// An Enumerable list of objects
IEnumerable GetChildren(int id);
-
+
///
/// Gets a collection of objects by Parent Id
///
@@ -251,7 +225,7 @@ namespace Umbraco.Core.Services
/// An Enumerable list of objects
IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter);
-
+
///
/// Gets a collection of objects by Parent Id
///
@@ -323,7 +297,7 @@ namespace Umbraco.Core.Services
///
/// Saves a collection of objects.
- ///
+ ///
/// Collection of to save
/// Optional Id of the User saving the Content
/// Optional boolean indicating whether or not to raise events.
@@ -442,13 +416,6 @@ namespace Umbraco.Core.Services
/// True if the content has any published version otherwise False
bool HasPublishedVersion(int id);
- ///
- /// Re-Publishes all Content
- ///
- /// Optional Id of the User issueing the publishing
- /// True if publishing succeeded, otherwise False
- bool RePublishAll(int userId = 0);
-
///
/// Publishes a single object
///
@@ -520,11 +487,11 @@ namespace Umbraco.Core.Services
///
/// Please note that this method will completely remove the Content from the database
/// The to delete
- /// Optional Id of the User deleting the Content
+ /// Optional Id of the User deleting the Content
void Delete(IContent content, int userId = 0);
///
- /// Copies an object by creating a new Content object of the same type and copies all data from the current
+ /// Copies an object by creating a new Content object of the same type and copies all data from the current
/// to the new copy, which is returned. Recursively copies all children.
///
/// The to copy
@@ -535,7 +502,7 @@ namespace Umbraco.Core.Services
IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0);
///
- /// Copies an object by creating a new Content object of the same type and copies all data from the current
+ /// Copies an object by creating a new Content object of the same type and copies all data from the current
/// to the new copy which is returned.
///
/// The to copy
diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs
index 14f48bb156..e0d4b3549e 100644
--- a/src/Umbraco.Core/Services/IContentTypeService.cs
+++ b/src/Umbraco.Core/Services/IContentTypeService.cs
@@ -24,17 +24,5 @@ namespace Umbraco.Core.Services
///
///
IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes);
-
- ///
- /// Generates the complete (simplified) XML DTD.
- ///
- /// The DTD as a string
- string GetDtd();
-
- ///
- /// Generates the complete XML DTD without the root.
- ///
- /// The DTD as a string
- string GetContentTypesDtd();
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs
index b231119561..e06cf815c1 100644
--- a/src/Umbraco.Core/Services/IMediaService.cs
+++ b/src/Umbraco.Core/Services/IMediaService.cs
@@ -58,15 +58,6 @@ namespace Umbraco.Core.Services
///
public interface IMediaService : IService
{
- ///
- /// Rebuilds all xml content in the cmsContentXml table for all media
- ///
- ///
- /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures
- /// for all media
- ///
- void RebuildXmlStructures(params int[] contentTypeIds);
-
int Count(string mediaTypeAlias = null);
int CountChildren(int parentId, string mediaTypeAlias = null);
int CountDescendants(int parentId, string mediaTypeAlias = null);
diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs
index 6591cc0b74..96029c6a4d 100644
--- a/src/Umbraco.Core/Services/IMemberService.cs
+++ b/src/Umbraco.Core/Services/IMemberService.cs
@@ -13,15 +13,6 @@ namespace Umbraco.Core.Services
///
public interface IMemberService : IMembershipMemberService
{
- ///
- /// Rebuilds all xml content in the cmsContentXml table for all documents
- ///
- ///
- /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures
- /// for all content
- ///
- void RebuildXmlStructures(params int[] contentTypeIds);
-
///
/// Gets a list of paged objects
///
diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs
index 79f52d7b5c..8792c35852 100644
--- a/src/Umbraco.Core/Services/MediaService.cs
+++ b/src/Umbraco.Core/Services/MediaService.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -11,7 +10,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
-using Umbraco.Core.Strings;
+using Umbraco.Core.Services.Changes;
namespace Umbraco.Core.Services
{
@@ -20,10 +19,6 @@ namespace Umbraco.Core.Services
///
public class MediaService : RepositoryService, IMediaService, IMediaServiceOperations
{
- private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer();
- private readonly IDataTypeService _dataTypeService;
- private readonly IUserService _userService;
- private readonly IEnumerable _urlSegmentProviders;
private IMediaTypeService _mediaTypeService;
#region Constructors
@@ -31,19 +26,9 @@ namespace Umbraco.Core.Services
public MediaService(
IDatabaseUnitOfWorkProvider provider,
ILogger logger,
- IEventMessagesFactory eventMessagesFactory,
- IDataTypeService dataTypeService,
- IUserService userService,
- IEnumerable urlSegmentProviders)
+ IEventMessagesFactory eventMessagesFactory)
: base(provider, logger, eventMessagesFactory)
- {
- if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService));
- if (userService == null) throw new ArgumentNullException(nameof(userService));
- if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders));
- _dataTypeService = dataTypeService;
- _userService = userService;
- _urlSegmentProviders = urlSegmentProviders;
- }
+ { }
// don't change or remove this, will need it later
private IMediaTypeService MediaTypeService => _mediaTypeService;
@@ -275,10 +260,9 @@ namespace Umbraco.Core.Services
var repo = uow.CreateRepository();
repo.AddOrUpdate(media);
- // FIXME contentXml?!
- repo.AddOrUpdatePreviewXml(media, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, c));
Saved.RaiseEvent(new SaveEventArgs(media, false), this);
+ TreeChanged.RaiseEvent(new TreeChange(media, TreeChangeTypes.RefreshNode).ToEventArgs(), this);
}
Created.RaiseEvent(new NewEventArgs(media, false, media.ContentType.Alias, parent), this);
@@ -566,7 +550,7 @@ namespace Umbraco.Core.Services
/// Field to order by
/// Direction to order by
/// Flag to indicate when ordering by system field
- ///
+ ///
/// An Enumerable list of objects
public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter)
{
@@ -753,6 +737,8 @@ namespace Umbraco.Core.Services
if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(media, evtMsgs), this))
return OperationStatus.Attempt.Cancel(evtMsgs);
+ var isNew = media.IsNewEntity();
+
using (var uow = UowProvider.CreateUnitOfWork())
{
uow.WriteLock(Constants.Locks.MediaTree);
@@ -761,17 +747,13 @@ namespace Umbraco.Core.Services
if (media.HasIdentity == false)
media.CreatorId = userId;
repository.AddOrUpdate(media);
- repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m));
-
- // generate preview for blame history?
- if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled)
- repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m));
-
uow.Complete();
}
if (raiseEvents)
Saved.RaiseEvent(new SaveEventArgs(media, false, evtMsgs), this);
+ var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode;
+ TreeChanged.RaiseEvent(new TreeChange(media, changeType).ToEventArgs(), this);
Audit(AuditType.Save, "Save Media performed by user", userId, media.Id);
return OperationStatus.Attempt.Succeed(evtMsgs);
@@ -802,6 +784,9 @@ namespace Umbraco.Core.Services
if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(mediasA, evtMsgs), this))
return OperationStatus.Attempt.Cancel(evtMsgs);
+ var treeChanges = mediasA.Select(x => new TreeChange(x,
+ x.IsNewEntity() ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode));
+
using (var uow = UowProvider.CreateUnitOfWork())
{
uow.WriteLock(Constants.Locks.MediaTree);
@@ -811,11 +796,6 @@ namespace Umbraco.Core.Services
if (media.HasIdentity == false)
media.CreatorId = userId;
repository.AddOrUpdate(media);
- repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m));
-
- // generate preview for blame history?
- if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled)
- repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m));
}
uow.Complete();
@@ -823,6 +803,7 @@ namespace Umbraco.Core.Services
if (raiseEvents)
Saved.RaiseEvent(new SaveEventArgs(mediasA, false, evtMsgs), this);
+ TreeChanged.RaiseEvent(treeChanges.ToEventArgs(), this);
Audit(AuditType.Save, "Bulk Save media performed by user", userId == -1 ? 0 : userId, Constants.System.Root);
return OperationStatus.Attempt.Succeed(evtMsgs);
@@ -870,6 +851,7 @@ namespace Umbraco.Core.Services
DeleteLocked(repository, media);
uow.Complete();
+ TreeChanged.RaiseEvent(new TreeChange(media, TreeChangeTypes.Remove).ToEventArgs(), this);
}
Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id);
@@ -1004,6 +986,7 @@ namespace Umbraco.Core.Services
PerformMoveLocked(repository, media, Constants.System.RecycleBinMedia, null, userId, moves, true);
uow.Complete();
+ TreeChanged.RaiseEvent(new TreeChange(media, TreeChangeTypes.RefreshBranch).ToEventArgs(), this);
}
var moveInfo = moves
@@ -1053,6 +1036,7 @@ namespace Umbraco.Core.Services
PerformMoveLocked(repository, media, parentId, parent, userId, moves, trashed);
uow.Complete();
+ TreeChanged.RaiseEvent(new TreeChange(media, TreeChangeTypes.RefreshBranch).ToEventArgs(), this);
}
var moveInfo = moves //changes
@@ -1145,6 +1129,7 @@ namespace Umbraco.Core.Services
EmptiedRecycleBin.RaiseEvent(new RecycleBinEventArgs(nodeObjectType, true), this);
uow.Complete();
+ TreeChanged.RaiseEvent(deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs(), this);
}
Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, Constants.System.RecycleBinMedia);
@@ -1193,11 +1178,6 @@ namespace Umbraco.Core.Services
// save
saved.Add(media);
repository.AddOrUpdate(media);
- repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m));
-
- // generate preview for blame history?
- if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled)
- repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, _urlSegmentProviders, m));
}
uow.Complete();
@@ -1205,6 +1185,7 @@ namespace Umbraco.Core.Services
if (raiseEvents)
Saved.RaiseEvent(new SaveEventArgs