U4-4847 Refactor ContentService (#1266)

* U4-4748 - refactor Content-, Media- and MemberTypeRepository

* Cleanup Attempt

* Cleanup OperationStatus

* U4-4748 - refactor Content-, Media- and MemberTypeService

* U4-4748 - cleanup locking

* U4-4748 - refactor Content-, Media- and MemberRepository

* U4-4748 - refactor ContentService (in progress)

* U4-4748 - all unit of work must be completed

* U4-4748 - refactor locks, fix tests

* U4-4748 - deal with fixmes

* U4-4748 - lock table migration

* Update UmbracoVersion

* Fix AuthorizeUpgrade

* U4-4748 - cleanup+bugfix lock objects

* U4-4748 - bugfix

* updates a string interpolation
This commit is contained in:
Stephan
2016-05-18 10:55:19 +02:00
committed by Shannon Deminick
parent 12f4873c90
commit ddf38407d8
139 changed files with 7539 additions and 6981 deletions

View File

@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -17,9 +14,6 @@ using Umbraco.Core.IO;
using Umbraco.Core.Media;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Strings;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models
@@ -43,175 +37,184 @@ namespace Umbraco.Core.Models
}
/// <summary>
/// Determines if the item should be persisted at all
/// Determines whether the content should be persisted.
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <remarks>
/// In one particular case, a content item shouldn't be persisted:
/// * The item exists and is published
/// * A call to ContentService.Save is made
/// * The item has not been modified whatsoever apart from changing it's published status from published to saved
///
/// In this case, there is no reason to make any database changes at all
/// </remarks>
/// <param name="entity">The content.</param>
/// <returns>True is the content should be persisted, otherwise false.</returns>
/// <remarks>See remarks in overload.</remarks>
internal static bool RequiresSaving(this IContent entity)
{
var publishedState = ((Content)entity).PublishedState;
return RequiresSaving(entity, publishedState);
return RequiresSaving(entity, ((Content) entity).PublishedState);
}
/// <summary>
/// Determines if the item should be persisted at all
/// Determines whether the content should be persisted.
/// </summary>
/// <param name="entity"></param>
/// <param name="publishedState"></param>
/// <returns></returns>
/// <param name="entity">The content.</param>
/// <param name="publishedState">The published state of the content.</param>
/// <returns>True is the content should be persisted, otherwise false.</returns>
/// <remarks>
/// In one particular case, a content item shouldn't be persisted:
/// * The item exists and is published
/// * A call to ContentService.Save is made
/// * The item has not been modified whatsoever apart from changing it's published status from published to saved
///
/// In this case, there is no reason to make any database changes at all
/// This is called by the repository when persisting an existing content, to
/// figure out whether it needs to persist the content at all.
/// </remarks>
internal static bool RequiresSaving(this IContent entity, PublishedState publishedState)
{
var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished;
//check if any user prop has changed
var propertyValueChanged = entity.IsAnyUserPropertyDirty();
//We need to know if any other property apart from Published was changed here
//don't create a new version if the published state has changed to 'Save' but no data has actually been changed
if (publishedChanged && entity.Published == false && propertyValueChanged == false)
// note: publishedState is always the entity's PublishedState except for tests
var content = (Content) entity;
var userPropertyChanged = content.IsAnyUserPropertyDirty();
var dirtyProps = content.GetDirtyProperties();
//var contentPropertyChanged = content.IsEntityDirty();
var contentPropertyChangedExceptPublished = dirtyProps.Any(x => x != "Published");
// we don't want to save (write to DB) if we are "saving" either a published content
// (.Saving) or an unpublished content (.Unpublished) and strictly nothing has changed
var noSave = (publishedState == PublishedState.Saving || publishedState == PublishedState.Unpublished)
&& userPropertyChanged == false
&& contentPropertyChangedExceptPublished == false;
return noSave == false;
}
/// <summary>
/// Determines whether a new version of the content should be created.
/// </summary>
/// <param name="entity">The content.</param>
/// <returns>True if a new version should be created, otherwise false.</returns>
/// <remarks>See remarks in overload.</remarks>
internal static bool RequiresNewVersion(this IContent entity)
{
return RequiresNewVersion(entity, ((Content) entity).PublishedState);
}
/// <summary>
/// Determines whether a new version of the content should be created.
/// </summary>
/// <param name="entity">The content.</param>
/// <param name="publishedState">The published state of the content.</param>
/// <returns>True if a new version should be created, otherwise false.</returns>
/// <remarks>
/// This is called by the repository when persisting an existing content, to
/// figure out whether it needs to create a new version for that content.
/// A new version needs to be created when:
/// * The publish status is changed
/// * The language is changed
/// * A content property is changed (? why ?)
/// * The item is already published and is being published again and any property value is changed (to enable a rollback)
/// </remarks>
internal static bool RequiresNewVersion(this IContent entity, PublishedState publishedState)
{
// note: publishedState is always the entity's PublishedState except for tests
// read
// http://issues.umbraco.org/issue/U4-2589 (save & publish & creating new versions)
// http://issues.umbraco.org/issue/U4-3404 (pressing preview does save then preview)
// http://issues.umbraco.org/issue/U4-5510 (previewing & creating new versions)
//
// slightly modifying the rules to make more sense (marked with CHANGE)
// but should respect the result of the discussions in those issues
// figure out whether .Language has changed
// this language stuff was an old POC and should be removed
var hasLanguageChanged = entity.IsPropertyDirty("Language");
if (hasLanguageChanged)
return true; // language change => new version
var content = (Content) entity;
//var contentPropertyChanged = content2.IsEntityDirty();
var userPropertyChanged = content.IsAnyUserPropertyDirty();
var dirtyProps = content.GetDirtyProperties();
var contentPropertyChangedExceptPublished = dirtyProps.Any(x => x != "Published");
var wasPublished = content.PublishedOriginal;
switch (publishedState)
{
//at this point we need to check if any non property value has changed that wasn't the published state
var changedProps = ((TracksChangesEntityBase)entity).GetDirtyProperties();
if (changedProps.Any(x => x != "Published") == false)
{
case PublishedState.Publishing:
// changed state, publishing either a published or an unpublished version:
// DO create a new (published) version IF it was published already AND
// anything has changed, else can reuse the current version
return (contentPropertyChangedExceptPublished || userPropertyChanged) && wasPublished;
case PublishedState.Unpublishing:
// changed state, unpublishing a published version:
// DO create a new (draft) version and preserve the (formerly) published
// version for rollback purposes IF the version that's being saved is the
// published version, else it's a draft that we can reuse
return wasPublished;
case PublishedState.Saving:
// changed state, saving a published version:
// DO create a new (draft) version and preserve the published version IF
// anything has changed, else do NOT create a new version (pointless)
return contentPropertyChangedExceptPublished || userPropertyChanged;
case PublishedState.Published:
// unchanged state, saving a published version:
// (can happen eg when moving content, never otherwise)
// do NOT create a new version as we're just saving after operations (eg
// move) that cannot be rolled back anyway - ensure that's really it
if (userPropertyChanged)
throw new InvalidOperationException("Invalid PublishedState \"Published\" with user property changes.");
return false;
}
case PublishedState.Unpublished:
// unchanged state, saving an unpublished version:
// do NOT create a new version for user property changes,
// BUT create a new version in case of content property changes, for
// rollback purposes
return contentPropertyChangedExceptPublished;
default:
throw new NotSupportedException();
}
return true;
}
/// <summary>
/// Determines if a new version should be created
/// Determines whether the database published flag should be cleared for versions
/// other than this content version.
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <param name="entity">The content.</param>
/// <returns>True if the published flag should be cleared, otherwise false.</returns>
/// <returns>See remarks in overload.</returns>
internal static bool RequiresClearPublishedFlag(this IContent entity)
{
var publishedState = ((Content) entity).PublishedState;
var requiresNewVersion = entity.RequiresNewVersion(publishedState);
return entity.RequiresClearPublishedFlag(publishedState, requiresNewVersion);
}
/// <summary>
/// Determines whether the database published flag should be cleared for versions
/// other than this content version.
/// </summary>
/// <param name="entity">The content.</param>
/// <param name="publishedState">The published state of the content.</param>
/// <param name="isNewVersion">Indicates whether the content is a new version.</param>
/// <returns>True if the published flag should be cleared, otherwise false.</returns>
/// <remarks>
/// A new version needs to be created when:
/// * The publish status is changed
/// * The language is changed
/// * The item is already published and is being published again and any property value is changed (to enable a rollback)
/// This is called by the repository when persisting an existing content, to
/// figure out whether it needs to clear the published flag for other versions.
/// </remarks>
internal static bool ShouldCreateNewVersion(this IContent entity)
internal static bool RequiresClearPublishedFlag(this IContent entity, PublishedState publishedState, bool isNewVersion)
{
var publishedState = ((Content)entity).PublishedState;
return ShouldCreateNewVersion(entity, publishedState);
}
// note: publishedState is always the entity's PublishedState except for tests
/// <summary>
/// Returns a list of all dirty user defined properties
/// </summary>
/// <returns></returns>
public static IEnumerable<string> GetDirtyUserProperties(this IContentBase entity)
{
return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias);
}
public static bool IsAnyUserPropertyDirty(this IContentBase entity)
{
return entity.Properties.Any(x => x.IsDirty());
}
public static bool WasAnyUserPropertyDirty(this IContentBase entity)
{
return entity.Properties.Any(x => x.WasDirty());
}
/// <summary>
/// Determines if a new version should be created
/// </summary>
/// <param name="entity"></param>
/// <param name="publishedState"></param>
/// <returns></returns>
/// <remarks>
/// A new version needs to be created when:
/// * The publish status is changed
/// * The language is changed
/// * The item is already published and is being published again and any property value is changed (to enable a rollback)
/// </remarks>
internal static bool ShouldCreateNewVersion(this IContent entity, PublishedState publishedState)
{
//check if the published state has changed or the language
var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished;
var langChanged = entity.IsPropertyDirty("Language");
var contentChanged = publishedChanged || langChanged;
//check if any user prop has changed
var propertyValueChanged = entity.IsAnyUserPropertyDirty();
//return true if published or language has changed
if (contentChanged)
{
// new, published version => everything else must be cleared
if (isNewVersion && entity.Published)
return true;
}
//check if any content prop has changed
var contentDataChanged = ((Content)entity).IsEntityDirty();
// if that entity was published then that entity has the flag and
// it does not need to be cleared for other versions
// NOT TRUE when unpublishing we create a NEW version
//var wasPublished = ((Content)entity).PublishedOriginal;
//if (wasPublished)
// return false;
//return true if the item is published and a property has changed or if any content property has changed
return (propertyValueChanged && publishedState == PublishedState.Published) || contentDataChanged;
}
/// <summary>
/// Determines if the published db flag should be set to true for the current entity version and all other db
/// versions should have their flag set to false.
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <remarks>
/// This is determined by:
/// * If a new version is being created and the entity is published
/// * If the published state has changed and the entity is published OR the entity has been un-published.
/// </remarks>
internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity)
{
var publishedState = ((Content)entity).PublishedState;
return entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, entity.ShouldCreateNewVersion(publishedState));
}
/// <summary>
/// Determines if the published db flag should be set to true for the current entity version and all other db
/// versions should have their flag set to false.
/// </summary>
/// <param name="entity"></param>
/// <param name="publishedState"></param>
/// <param name="isCreatingNewVersion"></param>
/// <returns></returns>
/// <remarks>
/// This is determined by:
/// * If a new version is being created and the entity is published
/// * If the published state has changed and the entity is published OR the entity has been un-published.
/// </remarks>
internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity, PublishedState publishedState, bool isCreatingNewVersion)
{
if (isCreatingNewVersion && entity.Published)
{
return true;
}
//If Published state has changed then previous versions should have their publish state reset.
//If state has been changed to unpublished the previous versions publish state should also be reset.
if (entity.IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished))
{
return true;
}
return false;
// clear whenever we are publishing or unpublishing
// publishing: because there might be a previously published version, which needs to be cleared
// unpublishing: same - we might be a saved version, not the published one, which needs to be cleared
return publishedState == PublishedState.Publishing || publishedState == PublishedState.Unpublishing;
}
/// <summary>
@@ -312,9 +315,9 @@ namespace Umbraco.Core.Models
return content.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Contains(recycleBinId.ToInvariantString());
}
/// <summary>
/// Removes characters that are not valide XML characters from all entity properties
/// Removes characters that are not valide XML characters from all entity properties
/// of type string. See: http://stackoverflow.com/a/961504/5018
/// </summary>
/// <returns></returns>
@@ -454,7 +457,7 @@ namespace Umbraco.Core.Models
/// <param name="value">The <see cref="HttpPostedFileBase"/> containing the file that will be uploaded</param>
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value)
{
// Ensure we get the filename without the path in IE in intranet mode
// Ensure we get the filename without the path in IE in intranet mode
// http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
var fileName = value.FileName;
if (fileName.LastIndexOf(@"\") > 0)
@@ -597,7 +600,7 @@ namespace Umbraco.Core.Models
#endregion
#region User/Profile methods
/// <summary>
/// Gets the <see cref="IProfile"/> for the Creator of this media item.
/// </summary>
@@ -672,7 +675,7 @@ namespace Umbraco.Core.Models
///// <param name="tagGroup"></param>
///// <returns></returns>
///// <remarks>
///// The tags returned are only relavent for published content & saved media or members
///// The tags returned are only relavent for published content & saved media or members
///// </remarks>
//public static IEnumerable<ITag> GetTags(this IContentBase content, string propertyTypeAlias, string tagGroup = "default")
//{
@@ -909,5 +912,24 @@ namespace Umbraco.Core.Models
return ((PackagingService)(packagingService)).Export(member);
}
#endregion
#region Dirty
public static IEnumerable<string> GetDirtyUserProperties(this IContentBase entity)
{
return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias);
}
public static bool IsAnyUserPropertyDirty(this IContentBase entity)
{
return entity.Properties.Any(x => x.IsDirty());
}
public static bool WasAnyUserPropertyDirty(this IContentBase entity)
{
return entity.Properties.Any(x => x.WasDirty());
}
#endregion
}
}