diff --git a/src/Umbraco.Core/Composing/Lifetime.cs b/src/Umbraco.Core/Composing/Lifetime.cs
index 1a2cc3119a..012555be5e 100644
--- a/src/Umbraco.Core/Composing/Lifetime.cs
+++ b/src/Umbraco.Core/Composing/Lifetime.cs
@@ -12,31 +12,62 @@
/// or MS.DI, PerDependency in Autofac.
Transient,
+ // TODO: We need to fix this up, currently LightInject is the only one that behaves differently from all other containers.
+ // ... the simple fix would be to map this to PerScopeLifetime in LI but need to wait on a response here https://github.com/seesharper/LightInject/issues/494#issuecomment-518942625
+ //
+ // we use it for controllers, httpContextBase and other request scoped objects: MembershpHelper, TagQuery, UmbracoTreeSearcher and ISearchableTree
+ // - so that they are automatically disposed at the end of the scope (ie request)
+ // - not sure they should not be simply 'scoped'?
+
///
/// One unique instance per request.
///
- // TODO: review lifetimes for LightInject vs other containers
- // currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope'
- // but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped
- //
- // we use it for controllers, httpContextBase and umbracoContext
- // - so that they are automatically disposed at the end of the scope (ie request)
- // - not sure they should not be simply 'scoped'?
- //
- // Castle has an extra PerWebRequest something, and others use scope
- // what about Request before first request ie during application startup?
- // see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/
- // Castle ends up requiring a special scope manager too
- // see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4
- //
- // but maybe also - why are we requiring scoped services at startup?
+ ///
+ ///
+ /// Any instance created with this lifetime will be disposed at the end of a request.
+ ///
+ /// Corresponds to
+ ///
+ /// PerRequestLifeTime in LightInject - means transient but disposed at the end of the current web request.
+ /// see: https://github.com/seesharper/LightInject/issues/494#issuecomment-518493262
+ ///
+ ///
+ /// Scoped in MS.DI - means one per web request.
+ /// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes
+ ///
+ /// InstancePerRequest in Autofac - means one per web request.
+ /// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
+ /// But "Behind the scenes, though, it’s still just instance per matching lifetime scope."
+ ///
+ ///
+ /// LifestylePerWebRequest in Castle Windsor - means one per web request.
+ /// see https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-part-7-lifestyles.md#the-perwebrequest-lifestyle
+ ///
+ ///
Request,
///
- /// One unique instance per container scope.
+ /// One unique instance per scope.
///
- /// Corresponds to Scope in LightInject, Scoped in MS.DI
- /// or Castle Windsor, PerLifetimeScope in Autofac.
+ ///
+ ///
+ /// Any instance created with this lifetime will be disposed at the end of the current scope.
+ ///
+ /// Corresponds to
+ /// PerScopeLifetime in LightInject (when in a request, means one per web request)
+ ///
+ /// Scoped in MS.DI (when in a request, means one per web request)
+ /// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes
+ ///
+ /// InstancePerLifetimeScope in Autofac (when in a request, means one per web request)
+ /// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-lifetime-scope
+ /// Also note that Autofac's InstancePerRequest is the same as this, see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request
+ /// it says "Behind the scenes, though, it’s still just instance per matching lifetime scope."
+ ///
+ ///
+ /// LifestyleScoped in Castle Windsor
+ ///
+ ///
Scope,
///
diff --git a/src/Umbraco.Core/Exceptions/PanicException.cs b/src/Umbraco.Core/Exceptions/PanicException.cs
new file mode 100644
index 0000000000..4d41cafc65
--- /dev/null
+++ b/src/Umbraco.Core/Exceptions/PanicException.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Exceptions
+{
+ ///
+ /// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen
+ ///
+ [Serializable]
+ internal class PanicException : Exception
+ {
+ public PanicException()
+ {
+ }
+
+ public PanicException(string message) : base(message)
+ {
+ }
+
+ public PanicException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected PanicException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Mapping/UmbracoMapper.cs b/src/Umbraco.Core/Mapping/UmbracoMapper.cs
index 8915ebcf74..976f50b499 100644
--- a/src/Umbraco.Core/Mapping/UmbracoMapper.cs
+++ b/src/Umbraco.Core/Mapping/UmbracoMapper.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Mapping
{
@@ -259,7 +260,7 @@ namespace Umbraco.Core.Mapping
if (typeof(TTarget).IsArray)
{
var elementType = typeof(TTarget).GetElementType();
- if (elementType == null) throw new Exception("panic");
+ if (elementType == null) throw new PanicException("elementType == null which should never occur");
var targetArray = Array.CreateInstance(elementType, targetList.Count);
targetList.CopyTo(targetArray, 0);
target = targetArray;
@@ -382,7 +383,7 @@ namespace Umbraco.Core.Mapping
{
if (type.IsArray) return type.GetElementType();
if (type.IsGenericType) return type.GenericTypeArguments[0];
- throw new Exception("panic");
+ throw new PanicException($"Could not get enumerable or array type from {type}");
}
///
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs
index c04e7c8fda..89a71fdaf4 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
{
@@ -20,7 +21,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
case "Umbraco.NoEdit":
return Constants.PropertyEditors.Aliases.Label;
default:
- throw new Exception("panic");
+ throw new PanicException($"The alias {editorAlias} is not supported");
}
}
}
diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
index bf7228ca47..f9efc60142 100644
--- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
@@ -222,7 +222,13 @@ namespace Umbraco.Core.Models
return true;
}
- public static void UnpublishCulture(this IContent content, string culture = "*")
+ ///
+ /// Returns false if the culture is already unpublished
+ ///
+ ///
+ ///
+ ///
+ public static bool UnpublishCulture(this IContent content, string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
@@ -230,16 +236,31 @@ namespace Umbraco.Core.Models
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
- if (culture == "*") // all cultures
+
+ var keepProcessing = true;
+
+ if (culture == "*")
+ {
+ // all cultures
content.ClearPublishInfos();
- else // one single culture
- content.ClearPublishInfo(culture);
+ }
+ else
+ {
+ // one single culture
+ keepProcessing = content.ClearPublishInfo(culture);
+ }
+
- // property.PublishValues only publishes what is valid, variation-wise
- foreach (var property in content.Properties)
- property.UnpublishValues(culture);
+ if (keepProcessing)
+ {
+ // property.PublishValues only publishes what is valid, variation-wise
+ foreach (var property in content.Properties)
+ property.UnpublishValues(culture);
- content.PublishedState = PublishedState.Publishing;
+ content.PublishedState = PublishedState.Publishing;
+ }
+
+ return keepProcessing;
}
public static void ClearPublishInfos(this IContent content)
@@ -247,15 +268,24 @@ namespace Umbraco.Core.Models
content.PublishCultureInfos = null;
}
- public static void ClearPublishInfo(this IContent content, string culture)
+ ///
+ /// Returns false if the culture is already unpublished
+ ///
+ ///
+ ///
+ ///
+ public static bool ClearPublishInfo(this IContent content, string culture)
{
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
- content.PublishCultureInfos.Remove(culture);
-
- // set the culture to be dirty - it's been modified
- content.TouchCulture(culture);
+ var removed = content.PublishCultureInfos.Remove(culture);
+ if (removed)
+ {
+ // set the culture to be dirty - it's been modified
+ content.TouchCulture(culture);
+ }
+ return removed;
}
///
diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs
index 5f1fe6ed49..ed87c5f320 100644
--- a/src/Umbraco.Core/Models/IContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/IContentTypeBase.cs
@@ -101,7 +101,7 @@ namespace Umbraco.Core.Models
PropertyGroupCollection PropertyGroups { get; set; }
///
- /// Gets all local property types belonging to a group, across all local property groups.
+ /// Gets all local property types all local property groups or ungrouped.
///
IEnumerable PropertyTypes { get; }
diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
index 540abda2c5..318ccc916e 100644
--- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Models.PublishedContent
return type;
var def = type.GetGenericTypeDefinition();
if (def == null)
- throw new InvalidOperationException("panic");
+ throw new PanicException($"The type {type} has not generic type definition");
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
return def.MakeGenericType(args);
@@ -114,7 +114,7 @@ namespace Umbraco.Core.Models.PublishedContent
return type.FullName;
var def = type.GetGenericTypeDefinition();
if (def == null)
- throw new InvalidOperationException("panic");
+ throw new PanicException($"The type {type} has not generic type definition");
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
index f1473b5888..33dabe1b24 100644
--- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories
public static IEnumerable BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection dtos, int publishedVersionId, ILanguageRepository languageRepository)
{
var properties = new List();
- var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable) x);
+ var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable)x);
foreach (var propertyType in propertyTypes)
{
@@ -104,10 +104,14 @@ namespace Umbraco.Core.Persistence.Factories
/// The properties to map
///
/// out parameter indicating that one or more properties have been edited
- /// out parameter containing a collection of edited cultures when the contentVariation varies by culture
+ ///
+ /// Out parameter containing a collection of edited cultures when the contentVariation varies by culture.
+ /// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table.
+ ///
///
public static IEnumerable BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties,
- ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures)
+ ILanguageRepository languageRepository, out bool edited,
+ out HashSet editedCultures)
{
var propertyDataDtos = new List();
edited = false;
@@ -130,6 +134,9 @@ namespace Umbraco.Core.Persistence.Factories
// publishing = deal with edit and published values
foreach (var propertyValue in property.Values)
{
+ var isInvariantValue = propertyValue.Culture == null;
+ var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
+
// deal with published value
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
@@ -138,26 +145,36 @@ namespace Umbraco.Core.Persistence.Factories
if (propertyValue.EditedValue != null)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
+ // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
+ // administrator has previously changed the property type to be variant vs invariant.
+ // We need to check for this scenario here because otherwise the editedCultures and edited flags
+ // will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to
+ // only process edited cultures based on the current value type and how the property varies.
+ // The above logic will still persist the currently saved property value for each culture in case the admin
+ // decides to swap the property's variance again, in which case the edited flag will be recalculated.
+
+ if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue)
+ continue;
+
// use explicit equals here, else object comparison fails at comparing eg strings
var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue);
+
edited |= !sameValues;
- if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported
- && propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral
- && !sameValues) // and edited and published are different
+ if (entityVariesByCulture && !sameValues)
{
- editedCultures.Add(propertyValue.Culture); // report culture as edited
- }
+ if (isCultureValue)
+ {
+ editedCultures.Add(propertyValue.Culture); // report culture as edited
+ }
+ else if (isInvariantValue)
+ {
+ // flag culture as edited if it contains an edited invariant property
+ if (defaultCulture == null)
+ defaultCulture = languageRepository.GetDefaultIsoCode();
- // flag culture as edited if it contains an edited invariant property
- if (propertyValue.Culture == null //invariant property
- && !sameValues // and edited and published are different
- && entityVariesByCulture) //only when the entity is variant
- {
- if (defaultCulture == null)
- defaultCulture = languageRepository.GetDefaultIsoCode();
-
- editedCultures.Add(defaultCulture);
+ editedCultures.Add(defaultCulture);
+ }
}
}
}
@@ -167,7 +184,7 @@ namespace Umbraco.Core.Persistence.Factories
{
// not publishing = only deal with edit values
if (propertyValue.EditedValue != null)
- propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
+ propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
}
edited = true;
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index aeb4c3774f..7ab73f3f2d 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -512,31 +512,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var a in allPropertyDataDtos)
a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId];
- // prefetch configuration for tag properties
- var tagEditors = new Dictionary();
- foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values)
- {
- var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias;
- var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute();
- if (editorAttribute == null) continue;
- var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration;
- var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource)
- ? new TagConfiguration()
- : JsonConvert.DeserializeObject(tagConfigurationSource);
- if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter;
- tagEditors[editorAlias] = tagConfiguration;
- }
-
// now we have
// - the definitions
// - all property data dtos
- // - tag editors
+ // - tag editors (Actually ... no we don't since i removed that code, but we don't need them anyways it seems)
// and we need to build the proper property collections
- return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors);
+ return GetPropertyCollections(temps, allPropertyDataDtos);
}
- private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos, Dictionary tagConfigurations)
+ private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos)
where T : class, IContentBase
{
var result = new Dictionary();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
index 6b751eb8ff..4393d365f8 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
@@ -90,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto);
else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto);
- else throw new Exception("panic");
+ else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
contentTypes.Add(contentType.Id, contentType);
// map allowed content types
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
index 9d77eb0990..359b967dab 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
internal class ContentTypeRepository : ContentTypeRepositoryBase, IContentTypeRepository
{
- public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
- : base(scopeAccessor, cache, logger, commonRepository)
+ public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
+ : base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
@@ -56,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
- if (ids.Any()) throw new Exception("panic");
+ if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType();
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 22c9244d8f..f2efb03ba4 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
+using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -26,14 +27,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository
where TEntity : class, IContentTypeComposition
{
- protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
+ protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger)
{
CommonRepository = commonRepository;
+ LanguageRepository = languageRepository;
}
protected IContentTypeCommonRepository CommonRepository { get; }
-
+ protected ILanguageRepository LanguageRepository { get; }
protected abstract bool SupportsPublishing { get; }
public IEnumerable> Move(TEntity moving, EntityContainer container)
@@ -98,6 +100,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected void PersistNewBaseContentType(IContentTypeComposition entity)
{
+ ValidateVariations(entity);
+
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
//Cannot add a duplicate content type
@@ -163,11 +167,11 @@ AND umbracoNode.nodeObjectType = @objectType",
foreach (var allowedContentType in entity.AllowedContentTypes)
{
Database.Insert(new ContentTypeAllowedContentTypeDto
- {
- Id = entity.Id,
- AllowedId = allowedContentType.Id.Value,
- SortOrder = allowedContentType.SortOrder
- });
+ {
+ Id = entity.Id,
+ AllowedId = allowedContentType.Id.Value,
+ SortOrder = allowedContentType.SortOrder
+ });
}
@@ -214,6 +218,8 @@ AND umbracoNode.nodeObjectType = @objectType",
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
{
+ ValidateVariations(entity);
+
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
// ensure the alias is not used already
@@ -370,7 +376,7 @@ AND umbracoNode.id <> @id",
foreach (var propertyGroup in entity.PropertyGroups)
{
// insert or update group
- var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup,entity.Id);
+ var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup, entity.Id);
var groupId = propertyGroup.HasIdentity
? Database.Update(groupDto)
: Convert.ToInt32(Database.Insert(groupDto));
@@ -388,7 +394,7 @@ AND umbracoNode.id <> @id",
//check if the content type variation has been changed
var contentTypeVariationDirty = entity.IsPropertyDirty("Variations");
- var oldContentTypeVariation = (ContentVariation) dtoPk.Variations;
+ var oldContentTypeVariation = (ContentVariation)dtoPk.Variations;
var newContentTypeVariation = entity.Variations;
var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation;
if (contentTypeVariationChanging)
@@ -449,7 +455,7 @@ AND umbracoNode.id <> @id",
// via composition, with their original variations (ie not filtered by this
// content type variations - we need this true value to make decisions.
- foreach (var propertyType in ((ContentTypeCompositionBase) entity).RawComposedPropertyTypes)
+ foreach (var propertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
{
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
throw new NotSupportedException(); // TODO: support this
@@ -518,6 +524,25 @@ AND umbracoNode.id <> @id",
CommonRepository.ClearCache(); // always
}
+ ///
+ /// Ensures that no property types are flagged for a variance that is not supported by the content type itself
+ ///
+ ///
+ private void ValidateVariations(IContentTypeComposition entity)
+ {
+ //if the entity does not vary at all, then the property cannot have a variance value greater than it
+ if (entity.Variations == ContentVariation.Nothing)
+ {
+ foreach (var prop in entity.PropertyTypes)
+ {
+ if (prop.IsPropertyDirty(nameof(prop.Variations)) && prop.Variations > entity.Variations)
+ throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
+ }
+
+ }
+
+ }
+
private IEnumerable GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable all)
{
var impact = new List();
@@ -525,12 +550,12 @@ AND umbracoNode.id <> @id",
var tree = new Dictionary>();
foreach (var x in all)
- foreach (var y in x.ContentTypeComposition)
- {
- if (!tree.TryGetValue(y.Id, out var list))
- list = tree[y.Id] = new List();
- list.Add(x);
- }
+ foreach (var y in x.ContentTypeComposition)
+ {
+ if (!tree.TryGetValue(y.Id, out var list))
+ list = tree[y.Id] = new List();
+ list.Add(x);
+ }
var nset = new List();
do
@@ -572,7 +597,7 @@ AND umbracoNode.id <> @id",
// new property type, ignore
if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB))
continue;
- var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly
+ var oldVariation = (ContentVariation)oldVariationB; // NPoco cannot fetch directly
// only those property types that *actually* changed
var newVariation = propertyType.Variations;
@@ -636,7 +661,7 @@ AND umbracoNode.id <> @id",
var impactedL = impacted.Select(x => x.Id).ToList();
//Group by the "To" variation so we can bulk update in the correct batches
- foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
+ foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
{
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
var toVariation = grouping.Key;
@@ -646,10 +671,12 @@ AND umbracoNode.id <> @id",
case ContentVariation.Culture:
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
+ RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.Nothing:
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
+ RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
@@ -963,6 +990,205 @@ AND umbracoNode.id <> @id",
Database.Execute(sqlDelete);
}
+
+ }
+
+ ///
+ /// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed
+ ///
+ ///
+ ///
+ ///
+ /// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false
+ /// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each
+ /// property, culture and current/published version.
+ ///
+ private void RenormalizeDocumentEditedFlags(IReadOnlyCollection propertyTypeIds, IReadOnlyCollection contentTypeIds = null)
+ {
+ var defaultLang = LanguageRepository.GetDefaultId();
+
+ //This will build up a query to get the property values of both the current and the published version so that we can check
+ //based on the current variance of each item to see if it's 'edited' value should be true/false.
+
+ var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
+ if (whereInArgsCount > 2000)
+ throw new NotSupportedException("Too many property/content types.");
+
+ var propertySql = Sql()
+ .Select()
+ .AndSelect(x => x.NodeId, x => x.Current)
+ .AndSelect(x => x.Published)
+ .AndSelect(x => x.Variations)
+ .From()
+ .InnerJoin().On((left, right) => left.Id == right.VersionId)
+ .InnerJoin().On((left, right) => left.Id == right.PropertyTypeId);
+
+ if (contentTypeIds != null)
+ {
+ propertySql.InnerJoin().On((c, cversion) => c.NodeId == cversion.NodeId);
+ }
+
+ propertySql.LeftJoin().On((docversion, cversion) => cversion.Id == docversion.Id)
+ .Where((docversion, cversion) => cversion.Current || docversion.Published)
+ .WhereIn(x => x.PropertyTypeId, propertyTypeIds);
+
+ if (contentTypeIds != null)
+ {
+ propertySql.WhereIn(x => x.ContentTypeId, contentTypeIds);
+ }
+
+ propertySql
+ .OrderBy(x => x.NodeId)
+ .OrderBy(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId);
+
+ //keep track of this node/lang to mark or unmark a culture as edited
+ var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
+ //keep track of which node to mark or unmark as edited
+ var editedDocument = new Dictionary();
+ var nodeId = -1;
+ var propertyTypeId = -1;
+
+ PropertyValueVersionDto pubRow = null;
+
+ //This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data.
+ //Published data will always come before Current data based on the version id sort.
+ //There will only be one published row (max) and one current row per property.
+ foreach (var row in Database.Query(propertySql))
+ {
+ //make sure to reset on each node/property change
+ if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId)
+ {
+ nodeId = row.NodeId;
+ propertyTypeId = row.PropertyTypeId;
+ pubRow = null;
+ }
+
+ if (row.Published)
+ pubRow = row;
+
+ if (row.Current)
+ {
+ var propVariations = (ContentVariation)row.Variations;
+
+ //if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited
+ if (!propVariations.VariesByCulture() && row.LanguageId.HasValue
+ || propVariations.VariesByCulture() && !row.LanguageId.HasValue)
+ {
+ //Flag this as not edited for this node/lang if the key doesn't exist
+ if (!editedLanguageVersions.TryGetValue((row.NodeId, row.LanguageId), out _))
+ editedLanguageVersions.Add((row.NodeId, row.LanguageId), false);
+
+ //mark as false if the item doesn't exist, else coerce to true
+ editedDocument[row.NodeId] = editedDocument.TryGetValue(row.NodeId, out var edited) ? (edited |= false) : false;
+ }
+ else if (pubRow == null)
+ {
+ //this would mean that that this property is 'edited' since there is no published version
+ editedLanguageVersions[(row.NodeId, row.LanguageId)] = true;
+ editedDocument[row.NodeId] = true;
+ }
+ //compare the property values, if they differ from versions then flag the current version as edited
+ else if (IsPropertyValueChanged(pubRow, row))
+ {
+ //Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang
+ editedLanguageVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true;
+ editedDocument[row.NodeId] = true;
+ }
+
+ //reset
+ pubRow = null;
+ }
+ }
+
+ //lookup all matching rows in umbracoDocumentCultureVariation
+ var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
+ .SelectMany(_ => Database.Fetch(
+ Sql().Select().From()
+ .WhereIn(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
+ .WhereIn(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
+ //convert to dictionary with the same key type
+ .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
+
+ var toUpdate = new List();
+ foreach (var ev in editedLanguageVersions)
+ {
+ if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations))
+ {
+ //check if it needs updating
+ if (docVariations.Edited != ev.Value)
+ {
+ docVariations.Edited = ev.Value;
+ toUpdate.Add(docVariations);
+ }
+ }
+ else if (ev.Key.langId.HasValue)
+ {
+ //This should never happen! If a property culture is flagged as edited then the culture must exist at the document level
+ throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}");
+ }
+ }
+
+ //Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
+ foreach (var editValue in toUpdate.GroupBy(x => x.Edited))
+ {
+ Database.Execute(Sql().Update(u => u.Set(x => x.Edited, editValue.Key))
+ .WhereIn(x => x.Id, editValue.Select(x => x.Id)));
+ }
+
+ //Now bulk update the umbracoDocument table
+ foreach (var editValue in editedDocument.GroupBy(x => x.Value))
+ {
+ Database.Execute(Sql().Update(u => u.Set(x => x.Edited, editValue.Key))
+ .WhereIn(x => x.NodeId, editValue.Select(x => x.Key)));
+ }
+ }
+
+ private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row)
+ {
+ return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue
+ || !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue
+ || pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue
+ || pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue
+ || pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue;
+ }
+
+ private class NameCompareDto
+ {
+ public int NodeId { get; set; }
+ public int CurrentVersion { get; set; }
+ public int LanguageId { get; set; }
+ public string CurrentName { get; set; }
+ public string PublishedName { get; set; }
+ public int? PublishedVersion { get; set; }
+ public int Id { get; set; } // the Id of the DocumentCultureVariationDto
+ public bool Edited { get; set; }
+ }
+
+ private class PropertyValueVersionDto
+ {
+ public int VersionId { get; set; }
+ public int PropertyTypeId { get; set; }
+ public int? LanguageId { get; set; }
+ public string Segment { get; set; }
+ public int? IntValue { get; set; }
+
+ private decimal? _decimalValue;
+ [Column("decimalValue")]
+ public decimal? DecimalValue
+ {
+ get => _decimalValue;
+ set => _decimalValue = value?.Normalize();
+ }
+
+ public DateTime? DateValue { get; set; }
+ public string VarcharValue { get; set; }
+ public string TextValue { get; set; }
+
+ public int NodeId { get; set; }
+ public bool Current { get; set; }
+ public bool Published { get; set; }
+
+ public byte Variations { get; set; }
}
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index 30a2927cc8..344557d815 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -386,7 +386,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
- Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
+ Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
@@ -511,7 +511,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
documentVersionDto.Published = false; // non-published version
- Database.Insert(documentVersionDto);
+ Database.Insert(documentVersionDto);
}
// replace the property data (rather than updating)
@@ -571,7 +571,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
- Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
+ Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
@@ -1297,25 +1297,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
};
}
- private IEnumerable GetDocumentVariationDtos(IContent content, bool publishing, HashSet editedCultures)
+ private IEnumerable GetDocumentVariationDtos(IContent content, HashSet editedCultures)
{
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
foreach (var culture in allCultures)
- yield return new DocumentCultureVariationDto
+ {
+ var dto = new DocumentCultureVariationDto
{
NodeId = content.Id,
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
Name = content.GetCultureName(culture) ?? content.GetPublishName(culture),
-
- // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
-
Available = content.IsCultureAvailable(culture),
Published = content.IsCulturePublished(culture),
+ // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
Edited = content.IsCultureAvailable(culture) &&
(!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)))
};
+
+ yield return dto;
+ }
+
}
private class ContentVariation
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs
index 1abc75cf3a..512011b52c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
internal class MediaTypeRepository : ContentTypeRepositoryBase, IMediaTypeRepository
{
- public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
- : base(scopeAccessor, cache, logger, commonRepository)
+ public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
+ : base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
@@ -50,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
- if (ids.Any()) throw new Exception("panic");
+ if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType();
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs
index d96854743e..ee651819bf 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
internal class MemberTypeRepository : ContentTypeRepositoryBase, IMemberTypeRepository
{
- public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
- : base(scopeAccessor, cache, logger, commonRepository)
+ public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
+ : base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
@@ -57,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
- if (ids.Any()) throw new Exception("panic");
+ if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType();
}
diff --git a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs
index 2439c7c02e..6549f1a233 100644
--- a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs
+++ b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs
@@ -55,6 +55,7 @@ namespace Umbraco.Core.PropertyEditors
///
/// Gets the type of the dynamic configuration provider.
///
+ //TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562
public Type TagsConfigurationProviderType { get; }
}
}
diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs
index e49dcf4a12..5492f97409 100644
--- a/src/Umbraco.Core/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentService.cs
@@ -886,6 +886,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
@@ -894,13 +896,13 @@ namespace Umbraco.Core.Services.Implement
// if culture is '*', then publish them all (including variants)
//this will create the correct culture impact even if culture is * or null
- var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
+ var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
content.PublishCulture(impact);
- var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -921,6 +923,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
var evtMsgs = EventMessagesFactory.Get();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
@@ -928,25 +932,23 @@ namespace Umbraco.Core.Services.Implement
var varies = content.ContentType.VariesByCulture();
- if (cultures.Length == 0)
+ if (cultures.Length == 0 && !varies)
{
//no cultures specified and doesn't vary, so publish it, else nothing to publish
- return !varies
- ? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
- : new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
+ return SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents);
}
if (cultures.Any(x => x == null || x == "*"))
throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
- var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x)));
+ var impacts = cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x)));
// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
foreach (var impact in impacts)
content.PublishCulture(impact);
- var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -986,6 +988,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
@@ -993,26 +997,39 @@ namespace Umbraco.Core.Services.Implement
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
{
+ // It's important to understand that when the document varies by culture but the "*" is used,
+ // we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected
+ // because we don't want to actually unpublish every culture and then the document, we just want everything
+ // to be non-routable so that when it's re-published all variants were as they were.
+
content.PublishedState = PublishedState.Unpublishing;
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
+ scope.Complete();
+ return result;
}
else
{
- // If the culture we want to unpublish was already unpublished, nothing to do.
- // To check for that we need to lookup the persisted content item
- var persisted = content.HasIdentity ? GetById(content.Id) : null;
+ // Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
+ // essentially be re-publishing the document with the requested culture removed.
+ // The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished
+ // and will then unpublish the document accordingly.
+ // If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist)
+ var removed = content.UnpublishCulture(culture);
- if (persisted != null && !persisted.IsCulturePublished(culture))
+ //save and publish any changes
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
+
+ scope.Complete();
+
+ // In one case the result will be PublishStatusType.FailedPublishNothingToPublish which means that no cultures
+ // were specified to be published which will be the case when removed is false. In that case
+ // we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before).
+ if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed)
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
- // unpublish the culture
- content.UnpublishCulture(culture);
+ return result;
}
-
- var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId);
- scope.Complete();
- return result;
}
-
}
///
@@ -1047,15 +1064,35 @@ namespace Umbraco.Core.Services.Implement
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
- var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
+ var allLangs = _languageRepository.GetMany().ToList();
+
+ var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
}
+ ///
+ /// Handles a lot of business logic cases for how the document should be persisted
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for pending scheduled publishing, etc... is dealt with in this method.
+ /// There is quite a lot of cases to take into account along with logic that needs to deal with scheduled saving/publishing, branch saving/publishing, etc...
+ ///
+ ///
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
- ContentSavingEventArgs saveEventArgs,
- int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
+ ContentSavingEventArgs saveEventArgs, IReadOnlyCollection allLangs,
+ int userId = Constants.Security.SuperUserId,
+ bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
{
if (scope == null) throw new ArgumentNullException(nameof(scope));
if (content == null) throw new ArgumentNullException(nameof(content));
@@ -1070,8 +1107,8 @@ namespace Umbraco.Core.Services.Implement
if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing)
content.PublishedState = PublishedState.Publishing;
- // state here is either Publishing or Unpublishing
- // (even though, Publishing to unpublish a culture may end up unpublishing everything)
+ // State here is either Publishing or Unpublishing
+ // Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
var publishing = content.PublishedState == PublishedState.Publishing;
var unpublishing = content.PublishedState == PublishedState.Unpublishing;
@@ -1088,6 +1125,18 @@ namespace Umbraco.Core.Services.Implement
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
var previouslyPublished = content.HasIdentity && content.Published;
+ //inline method to persist the document with the documentRepository since this logic could be called a couple times below
+ void SaveDocument(IContent c)
+ {
+ // save, always
+ if (c.HasIdentity == false)
+ c.CreatorId = userId;
+ c.WriterId = userId;
+
+ // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
+ _documentRepository.Save(c);
+ }
+
if (publishing)
{
//determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
@@ -1097,11 +1146,25 @@ namespace Umbraco.Core.Services.Implement
: null;
// ensure that the document can be published, and publish handling events, business rules, etc
- publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs);
+ publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs, allLangs);
if (publishResult.Success)
{
// note: StrategyPublish flips the PublishedState to Publishing!
publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs);
+
+ //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole
+ if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0)
+ {
+ // This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures
+ // so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that
+ // the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to
+ // persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can
+ // mark the document for Unpublishing.
+ SaveDocument(content);
+
+ //set the flag to unpublish and continue
+ unpublishing = content.Published; // if not published yet, nothing to do
+ }
}
else
{
@@ -1162,13 +1225,8 @@ namespace Umbraco.Core.Services.Implement
}
}
- // save, always
- if (content.HasIdentity == false)
- content.CreatorId = userId;
- content.WriterId = userId;
-
- // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
- _documentRepository.Save(content);
+ //Persist the document
+ SaveDocument(content);
// raise the Saved event, always
if (raiseEvents)
@@ -1186,17 +1244,34 @@ namespace Umbraco.Core.Services.Implement
if (culturesUnpublishing != null)
{
- //If we are here, it means we tried unpublishing a culture but it was mandatory so now everything is unpublished
- var langs = string.Join(", ", _languageRepository.GetMany()
+ // This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
+
+ var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
- //log that the whole content item has been unpublished due to mandatory culture unpublished
- Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
- }
- else
- Audit(AuditType.Unpublish, userId, content.Id);
+ if (publishResult == null)
+ throw new PanicException("publishResult == null - should not happen");
+
+ switch(publishResult.Result)
+ {
+ case PublishResultType.FailedPublishMandatoryCultureMissing:
+ //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)
+
+ //log that the whole content item has been unpublished due to mandatory culture unpublished
+ Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
+ return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, evtMsgs, content);
+ case PublishResultType.SuccessUnpublishCulture:
+ //occurs when the last culture is unpublished
+
+ Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)");
+ return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content);
+ }
+
+ }
+
+ Audit(AuditType.Unpublish, userId, content.Id);
return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
}
@@ -1236,7 +1311,7 @@ namespace Umbraco.Core.Services.Implement
case PublishResultType.SuccessPublishCulture:
if (culturesPublishing != null)
{
- var langs = string.Join(", ", _languageRepository.GetMany()
+ var langs = string.Join(", ", allLangs
.Where(x => culturesPublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs);
@@ -1245,7 +1320,7 @@ namespace Umbraco.Core.Services.Implement
case PublishResultType.SuccessUnpublishCulture:
if (culturesUnpublishing != null)
{
- var langs = string.Join(", ", _languageRepository.GetMany()
+ var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
@@ -1259,14 +1334,14 @@ namespace Umbraco.Core.Services.Implement
// should not happen
if (branchOne && !branchRoot)
- throw new Exception("panic");
+ throw new PanicException("branchOne && !branchRoot - should not happen");
//if publishing didn't happen or if it has failed, we still need to log which cultures were saved
if (!branchOne && (publishResult == null || !publishResult.Success))
{
if (culturesChanging != null)
{
- var langs = string.Join(", ", _languageRepository.GetMany()
+ var langs = string.Join(", ", allLangs
.Where(x => culturesChanging.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
@@ -1297,6 +1372,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
foreach (var d in _documentRepository.GetContentForRelease(date))
{
PublishResult result;
@@ -1325,7 +1402,7 @@ namespace Umbraco.Core.Services.Implement
//publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
Property[] invalidProperties = null;
- var impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(culture));
+ var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture));
var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
if (invalidProperties != null && invalidProperties.Length > 0)
Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
@@ -1340,7 +1417,7 @@ namespace Umbraco.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
- result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
+ result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
if (result.Success == false)
@@ -1390,7 +1467,7 @@ namespace Umbraco.Core.Services.Implement
d.UnpublishCulture(c);
}
- result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
+ result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId);
if (result.Success == false)
Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
yield return result;
@@ -1416,7 +1493,7 @@ namespace Umbraco.Core.Services.Implement
}
// utility 'PublishCultures' func used by SaveAndPublishBranch
- private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish)
+ private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish, IReadOnlyCollection allLangs)
{
//TODO: This does not support being able to return invalid property details to bubble up to the UI
@@ -1426,7 +1503,7 @@ namespace Umbraco.Core.Services.Implement
{
return culturesToPublish.All(culture =>
{
- var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
+ var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
});
}
@@ -1535,7 +1612,7 @@ namespace Umbraco.Core.Services.Implement
internal IEnumerable SaveAndPublishBranch(IContent document, bool force,
Func> shouldPublish,
- Func, bool> publishCultures,
+ Func, IReadOnlyCollection, bool> publishCultures,
int userId = Constants.Security.SuperUserId)
{
if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish));
@@ -1549,6 +1626,8 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.ContentTree);
+ var allLangs = _languageRepository.GetMany().ToList();
+
if (!document.HasIdentity)
throw new InvalidOperationException("Cannot not branch-publish a new document.");
@@ -1557,7 +1636,7 @@ namespace Umbraco.Core.Services.Implement
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
// deal with the branch root - if it fails, abort
- var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
+ var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -1588,7 +1667,7 @@ namespace Umbraco.Core.Services.Implement
}
// no need to check path here, parent has to be published here
- result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
+ result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -1620,10 +1699,10 @@ namespace Umbraco.Core.Services.Implement
// publishValues: a function publishing values (using the appropriate PublishCulture calls)
private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document,
Func> shouldPublish,
- Func, bool> publishCultures,
+ Func, IReadOnlyCollection, bool> publishCultures,
bool isRoot,
ICollection publishedDocuments,
- EventMessages evtMsgs, int userId)
+ EventMessages evtMsgs, int userId, IReadOnlyCollection allLangs)
{
var culturesToPublish = shouldPublish(document);
if (culturesToPublish == null) // null = do not include
@@ -1636,13 +1715,13 @@ namespace Umbraco.Core.Services.Implement
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
// publish & check if values are valid
- if (!publishCultures(document, culturesToPublish))
+ if (!publishCultures(document, culturesToPublish, allLangs))
{
//TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
}
- var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot);
+ var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot);
if (result.Success)
publishedDocuments.Add(document);
return result;
@@ -2343,6 +2422,9 @@ namespace Umbraco.Core.Services.Implement
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Document), message, parameters));
}
+ private bool IsDefaultCulture(IReadOnlyCollection langs, string culture) => langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture));
+ private bool IsMandatoryCulture(IReadOnlyCollection langs, string culture) => langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture));
+
#endregion
#region Event Handlers
@@ -2497,7 +2579,9 @@ namespace Umbraco.Core.Services.Implement
///
///
///
- private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing, IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
+ private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing,
+ IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs,
+ IReadOnlyCollection allLangs)
{
// raise Publishing event
if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs()))
@@ -2510,7 +2594,7 @@ namespace Umbraco.Core.Services.Implement
var impactsToPublish = culturesPublishing == null
? new[] {CultureImpact.Invariant} //if it's null it's invariant
- : culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray();
+ : culturesPublishing.Select(x => CultureImpact.Explicit(x, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray();
// publish the culture(s)
if (!impactsToPublish.All(content.PublishCulture))
@@ -2531,11 +2615,17 @@ namespace Umbraco.Core.Services.Implement
if (culturesPublishing == null)
throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null.");
- if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published
+ if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0)
+ {
+ // no published cultures = cannot be published
+ // This will occur if for example, a culture that is already unpublished is sent to be unpublished again, or vice versa, in that case
+ // there will be nothing to publish/unpublish.
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
+ }
+
// missing mandatory culture = cannot be published
- var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
+ var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
var mandatoryMissing = mandatoryCultures.Any(x => !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase));
if (mandatoryMissing)
return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content);
@@ -2676,6 +2766,7 @@ namespace Umbraco.Core.Services.Implement
{
var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
+ //TODO: What is this check?? we just created this attempt and of course it is Success?!
if (attempt.Success == false)
return attempt;
diff --git a/src/Umbraco.Core/Services/PublishResultType.cs b/src/Umbraco.Core/Services/PublishResultType.cs
index 1a2b52f9c9..66c1e38267 100644
--- a/src/Umbraco.Core/Services/PublishResultType.cs
+++ b/src/Umbraco.Core/Services/PublishResultType.cs
@@ -49,6 +49,11 @@
///
SuccessUnpublishMandatoryCulture = 6,
+ ///
+ /// The specified document culture was unpublished, and was the last published culture in the document, therefore the document itself was unpublished.
+ ///
+ SuccessUnpublishLastCulture = 8,
+
#endregion
#region Success - Mixed
@@ -113,9 +118,9 @@
FailedPublishContentInvalid = FailedPublish | 8,
///
- /// The document could not be published because it has no publishing flags or values.
+ /// The document could not be published because it has no publishing flags or values or if its a variant document, no cultures were specified to be published.
///
- FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird
+ FailedPublishNothingToPublish = FailedPublish | 9,
///
/// The document could not be published because some mandatory cultures are missing.
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 6adce1944f..bdbe384b05 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -204,6 +204,7 @@
+
diff --git a/src/Umbraco.Examine/ContentValueSetBuilder.cs b/src/Umbraco.Examine/ContentValueSetBuilder.cs
index 44cef08813..9cbc311639 100644
--- a/src/Umbraco.Examine/ContentValueSetBuilder.cs
+++ b/src/Umbraco.Examine/ContentValueSetBuilder.cs
@@ -54,7 +54,7 @@ namespace Umbraco.Examine
{"updateDate", new object[] {c.UpdateDate}}, //Always add invariant updateDate
{"nodeName", (PublishedValuesOnly //Always add invariant nodeName
? c.PublishName?.Yield()
- : c?.Name.Yield()) ?? Enumerable.Empty()},
+ : c.Name?.Yield()) ?? Enumerable.Empty()},
{"urlName", urlValue?.Yield() ?? Enumerable.Empty()}, //Always add invariant urlName
{"path", c.Path?.Yield() ?? Enumerable.Empty()},
{"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty()},
diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs
index d97278f31c..0e451b298c 100644
--- a/src/Umbraco.Examine/ExamineExtensions.cs
+++ b/src/Umbraco.Examine/ExamineExtensions.cs
@@ -28,6 +28,31 @@ namespace Umbraco.Examine
///
internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled);
+ private static volatile bool _isUnlocked = false;
+ private static readonly object IsUnlockedLocker = new object();
+
+ ///
+ /// Unlocks all Lucene based indexes registered with the
+ ///
+ ///
+ /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before
+ /// either of these happens, we need to configure the indexes.
+ ///
+ internal static void EnsureUnlocked(this IExamineManager examineManager, IMainDom mainDom, ILogger logger)
+ {
+ if (!mainDom.IsMainDom) return;
+ if (_isUnlocked) return;
+
+ lock (IsUnlockedLocker)
+ {
+ //double check
+ if (_isUnlocked) return;
+
+ _isUnlocked = true;
+ examineManager.UnlockLuceneIndexes(logger);
+ }
+ }
+
//TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression
///
diff --git a/src/Umbraco.Examine/IndexRebuilder.cs b/src/Umbraco.Examine/IndexRebuilder.cs
index 43c309b9c5..786aecac71 100644
--- a/src/Umbraco.Examine/IndexRebuilder.cs
+++ b/src/Umbraco.Examine/IndexRebuilder.cs
@@ -5,7 +5,8 @@ using System.Threading.Tasks;
using Examine;
namespace Umbraco.Examine
-{
+{
+
///
/// Utility to rebuild all indexes ensuring minimal data queries
///
diff --git a/src/Umbraco.Examine/LuceneIndexDiagnostics.cs b/src/Umbraco.Examine/LuceneIndexDiagnostics.cs
new file mode 100644
index 0000000000..96363904b4
--- /dev/null
+++ b/src/Umbraco.Examine/LuceneIndexDiagnostics.cs
@@ -0,0 +1,83 @@
+using System.Collections.Generic;
+using Examine.LuceneEngine.Providers;
+using Umbraco.Core;
+using Umbraco.Core.Logging;
+using Lucene.Net.Store;
+using Umbraco.Core.IO;
+using System.Linq;
+
+namespace Umbraco.Examine
+{
+ public class LuceneIndexDiagnostics : IIndexDiagnostics
+ {
+ public LuceneIndexDiagnostics(LuceneIndex index, ILogger logger)
+ {
+ Index = index;
+ Logger = logger;
+ }
+
+ public LuceneIndex Index { get; }
+ public ILogger Logger { get; }
+
+ public int DocumentCount
+ {
+ get
+ {
+ try
+ {
+ return Index.GetIndexDocumentCount();
+ }
+ catch (AlreadyClosedException)
+ {
+ Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
+ return 0;
+ }
+ }
+ }
+
+ public int FieldCount
+ {
+ get
+ {
+ try
+ {
+ return Index.GetIndexFieldCount();
+ }
+ catch (AlreadyClosedException)
+ {
+ Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
+ return 0;
+ }
+ }
+ }
+
+ public Attempt IsHealthy()
+ {
+ var isHealthy = Index.IsHealthy(out var indexError);
+ return isHealthy ? Attempt.Succeed() : Attempt.Fail(indexError.Message);
+ }
+
+ public virtual IReadOnlyDictionary Metadata
+ {
+ get
+ {
+ var luceneDir = Index.GetLuceneDirectory();
+ var d = new Dictionary
+ {
+ [nameof(UmbracoExamineIndex.CommitCount)] = Index.CommitCount,
+ [nameof(UmbracoExamineIndex.DefaultAnalyzer)] = Index.DefaultAnalyzer.GetType().Name,
+ ["LuceneDirectory"] = luceneDir.GetType().Name
+ };
+
+ if (luceneDir is FSDirectory fsDir)
+ {
+ d[nameof(UmbracoExamineIndex.LuceneIndexFolder)] = fsDir.Directory.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/');
+ }
+
+ return d;
+ }
+ }
+
+
+ }
+}
diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj
index 95690c17e4..e28a8e674e 100644
--- a/src/Umbraco.Examine/Umbraco.Examine.csproj
+++ b/src/Umbraco.Examine/Umbraco.Examine.csproj
@@ -72,6 +72,7 @@
+
diff --git a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs
index fed5b9bae7..4a926deebe 100644
--- a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs
+++ b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs
@@ -7,73 +7,24 @@ using Umbraco.Core.Logging;
namespace Umbraco.Examine
{
- public class UmbracoExamineIndexDiagnostics : IIndexDiagnostics
+ public class UmbracoExamineIndexDiagnostics : LuceneIndexDiagnostics
{
private readonly UmbracoExamineIndex _index;
- private readonly ILogger _logger;
public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger)
+ : base(index, logger)
{
_index = index;
- _logger = logger;
}
- public int DocumentCount
+ public override IReadOnlyDictionary Metadata
{
get
{
- try
- {
- return _index.GetIndexDocumentCount();
- }
- catch (AlreadyClosedException)
- {
- _logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed");
- return 0;
- }
- }
- }
+ var d = base.Metadata.ToDictionary(x => x.Key, x => x.Value);
- public int FieldCount
- {
- get
- {
- try
- {
- return _index.GetIndexFieldCount();
- }
- catch (AlreadyClosedException)
- {
- _logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed");
- return 0;
- }
- }
- }
-
- public Attempt IsHealthy()
- {
- var isHealthy = _index.IsHealthy(out var indexError);
- return isHealthy ? Attempt.Succeed() : Attempt.Fail(indexError.Message);
- }
-
- public virtual IReadOnlyDictionary Metadata
- {
- get
- {
- var d = new Dictionary
- {
- [nameof(UmbracoExamineIndex.CommitCount)] = _index.CommitCount,
- [nameof(UmbracoExamineIndex.DefaultAnalyzer)] = _index.DefaultAnalyzer.GetType().Name,
- ["LuceneDirectory"] = _index.GetLuceneDirectory().GetType().Name,
- [nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler,
- [nameof(UmbracoExamineIndex.LuceneIndexFolder)] =
- _index.LuceneIndexFolder == null
- ? string.Empty
- : _index.LuceneIndexFolder.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'),
- [nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly,
- //There's too much info here
- //[nameof(UmbracoExamineIndexer.FieldDefinitionCollection)] = _index.FieldDefinitionCollection,
- };
+ d[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler;
+ d[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly;
if (_index.ValueSetValidator is ValueSetValidator vsv)
{
diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
index 53f150f140..f953b9cce6 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
@@ -29,10 +29,11 @@ namespace Umbraco.Tests.Persistence.Repositories
private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository)
{
+ var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
- contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
+ contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
@@ -40,9 +41,10 @@ namespace Umbraco.Tests.Persistence.Repositories
private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor)
{
+ var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
- var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
+ var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
return contentTypeRepository;
}
@@ -50,7 +52,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
- var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository);
+ var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
+ var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
return contentTypeRepository;
}
diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs
index fd797662c0..4d62ec8301 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs
@@ -67,8 +67,8 @@ namespace Umbraco.Tests.Persistence.Repositories
templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
- contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
+ contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs
index f00b2fd046..628f8d75a7 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs
@@ -23,8 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository);
languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository);
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
return domainRepository;
diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs
index 1d9cf6d022..e2123df9e3 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs
@@ -38,7 +38,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
- mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository);
+ var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
+ mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of());
return repository;
diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs
index f302d1d992..bb3286daed 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs
@@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var cacheHelper = AppCaches.Disabled;
var templateRepository = new TemplateRepository((IScopeAccessor)provider, cacheHelper, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
- return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
+ var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches, Logger);
+ return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
}
private EntityContainerRepository CreateContainerRepository(IScopeProvider provider)
diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs
index a5f7f08f22..17b16ad7ab 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs
@@ -31,7 +31,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var accessor = (IScopeAccessor) provider;
var templateRepository = Mock.Of();
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
+ var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
+ memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger);
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of());
diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs
index 79e8e43804..4b9f3096ce 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs
@@ -24,7 +24,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = Mock.Of();
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches);
- return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository);
+ var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of());
+ return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository, languageRepository);
}
[Test]
diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs
index 803eff25af..56041c24aa 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs
@@ -308,8 +308,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs
index b6cc4dc50d..e3de2c2892 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs
@@ -956,8 +956,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
- contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
@@ -968,7 +968,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
- mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
+ var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
+ mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of());
return repository;
}
diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs
index 13cbd463fb..b0f9a5335b 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs
@@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches);
- var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
+ var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage");
diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs
index b550091591..3e5919d7f3 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs
@@ -26,7 +26,8 @@ namespace Umbraco.Tests.Persistence.Repositories
var accessor = (IScopeAccessor) provider;
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository);
+ var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
+ mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository, languageRepository);
var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of());
var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of());
return repository;
@@ -44,8 +45,8 @@ namespace Umbraco.Tests.Persistence.Repositories
templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs
index ed5e6073ac..ef80672baf 100644
--- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs
+++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs
@@ -166,8 +166,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
- var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
+ var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -200,8 +200,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
- var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
+ var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -232,8 +232,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches);
- var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
- var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
+ var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
+ var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
@@ -267,8 +267,8 @@ namespace Umbraco.Tests.Services
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
- var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository);
- var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
+ var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
+ var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
// Act
diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs
index 222f40aeed..7cfc650510 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs
@@ -717,21 +717,8 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Unpublish_Content_Variation()
{
- // Arrange
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
- var langUk = new Language("en-GB") { IsDefault = true };
- var langFr = new Language("fr-FR");
-
- ServiceContext.LocalizationService.Save(langFr);
- ServiceContext.LocalizationService.Save(langUk);
-
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
-
- IContent content = new Content("content", Constants.System.Root, contentType);
- content.SetCultureName("content-fr", langFr.IsoCode);
- content.SetCultureName("content-en", langUk.IsoCode);
content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault));
content.PublishCulture(CultureImpact.Explicit(langUk.IsoCode, langUk.IsDefault));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
@@ -761,6 +748,185 @@ namespace Umbraco.Tests.Services
}
+ [Test]
+ public void Can_Publish_Culture_After_Last_Culture_Unpublished()
+ {
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //first culture
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //last culture
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id); //reget
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+
+ }
+
+
+
+ [Test]
+ public void Unpublish_All_Cultures_Has_Unpublished_State()
+ {
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(published.Success);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //first culture
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.AreEqual(PublishedState.Published, content.PublishedState); //still published
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+
+ unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //last culture
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+ Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //the last culture was unpublished so the document should also reflect this
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+ Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking
+ Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+ }
+
+ [Test]
+ public void Unpublishing_Mandatory_Language_Unpublishes_Document()
+ {
+ var langUk = new Language("en-GB") { IsDefault = true, IsMandatory = true };
+ var langFr = new Language("fr-FR");
+
+ ServiceContext.LocalizationService.Save(langFr);
+ ServiceContext.LocalizationService.Save(langUk);
+
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ IContent content = new Content("content", Constants.System.Root, contentType);
+ content.SetCultureName("content-fr", langFr.IsoCode);
+ content.SetCultureName("content-en", langUk.IsoCode);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(published.Success);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish mandatory lang
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishMandatoryCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); //remains published
+ Assert.AreEqual(PublishedState.Unpublished, content.PublishedState);
+ }
+
+ [Test]
+ public void Unpublishing_Already_Unpublished_Culture()
+ {
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(published.Success);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode);
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ //Change some data since Unpublish should always Save
+ content.SetCultureName("content-en-updated", langUk.IsoCode);
+
+ unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish again
+ Assert.IsTrue(unpublished.Success);
+ Assert.AreEqual(PublishResultType.SuccessUnpublishAlready, unpublished.Result);
+ Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode));
+
+ content = ServiceContext.ContentService.GetById(content.Id);
+ //ensure that even though the culture was already unpublished that the data was still persisted
+ Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
+ }
+
+ [Test]
+ public void Publishing_No_Cultures_Still_Saves()
+ {
+ var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
+
+ var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode });
+ Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
+ Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
+ Assert.IsTrue(published.Success);
+ Assert.AreEqual(PublishedState.Published, content.PublishedState);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+
+ //Change some data since SaveAndPublish should always Save
+ content.SetCultureName("content-en-updated", langUk.IsoCode);
+
+ var saved = ServiceContext.ContentService.SaveAndPublish(content, new string [] { }); //save without cultures
+ Assert.AreEqual(PublishResultType.FailedPublishNothingToPublish, saved.Result);
+
+ //re-get
+ content = ServiceContext.ContentService.GetById(content.Id);
+ //ensure that even though nothing was published that the data was still persisted
+ Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode));
+ }
+
+
[Test]
public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State()
{
@@ -811,17 +977,7 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures()
{
- // Arrange
-
- var langGB = new Language("en-GB") { IsDefault = true };
- var langFr = new Language("fr-FR");
-
- ServiceContext.LocalizationService.Save(langFr);
- ServiceContext.LocalizationService.Save(langGB);
-
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
+ CreateEnglishAndFrenchDocumentType(out var langUk, out var langFr, out var contentType);
IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode);
@@ -832,8 +988,8 @@ namespace Umbraco.Tests.Services
//re-get
content = ServiceContext.ContentService.GetById(content.Id);
- content.SetCultureName("content-en", langGB.IsoCode);
- published = ServiceContext.ContentService.SaveAndPublish(content, langGB.IsoCode);
+ content.SetCultureName("content-en", langUk.IsoCode);
+ published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode);
//audit log will only show that english was published
lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last();
Assert.AreEqual($"Published languages: English (United Kingdom)", lastLog.Comment);
@@ -3008,10 +3164,33 @@ namespace Umbraco.Tests.Services
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
- contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
+ contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
return repository;
}
+
+ private void CreateEnglishAndFrenchDocumentType(out Language langUk, out Language langFr, out ContentType contentType)
+ {
+ langUk = new Language("en-GB") { IsDefault = true };
+ langFr = new Language("fr-FR");
+ ServiceContext.LocalizationService.Save(langFr);
+ ServiceContext.LocalizationService.Save(langUk);
+
+ contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ }
+
+ private IContent CreateEnglishAndFrenchDocument(out Language langUk, out Language langFr, out ContentType contentType)
+ {
+ CreateEnglishAndFrenchDocumentType(out langUk, out langFr, out contentType);
+
+ IContent content = new Content("content", Constants.System.Root, contentType);
+ content.SetCultureName("content-fr", langFr.IsoCode);
+ content.SetCultureName("content-en", langUk.IsoCode);
+
+ return content;
+ }
}
}
diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
index a0b5f01a1f..f2a4368ae4 100644
--- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
@@ -53,360 +53,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(contentType.IsElement);
}
- [Test]
- public void Change_Content_Type_Variation_Clears_Redirects()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
- var contentType2 = MockedContentTypes.CreateBasicContentType("test");
- ServiceContext.ContentTypeService.Save(contentType2);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.Name = "Hello1";
- ServiceContext.ContentService.Save(doc);
-
- IContent doc2 = MockedContent.CreateBasicContent(contentType2);
- ServiceContext.ContentService.Save(doc2);
-
- ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
- ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
-
- Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
- Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
-
- //change variation
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
-
- Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
- Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
-
- }
-
- [Test]
- public void Change_Content_Type_From_Invariant_Variant()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.Name = "Hello1";
- doc.SetValue("title", "hello world");
- ServiceContext.ContentService.Save(doc);
-
- Assert.AreEqual("Hello1", doc.Name);
- Assert.AreEqual("hello world", doc.GetValue("title"));
-
- //change the content type to be variant, we will also update the name here to detect the copy changes
- doc.Name = "Hello2";
- ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
- Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
-
- //change back property type to be invariant, we will also update the name here to detect the copy changes
- doc.SetCultureName("Hello3", "en-US");
- ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("Hello3", doc.Name);
- Assert.AreEqual("hello world", doc.GetValue("title"));
- }
-
- [Test]
- public void Change_Content_Type_From_Variant_Invariant()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.SetCultureName("Hello1", "en-US");
- doc.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc);
-
- Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
-
- //change the content type to be invariant, we will also update the name here to detect the copy changes
- doc.SetCultureName("Hello2", "en-US");
- ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("Hello2", doc.Name);
- Assert.AreEqual("hello world", doc.GetValue("title"));
-
- //change back property type to be variant, we will also update the name here to detect the copy changes
- doc.Name = "Hello3";
- ServiceContext.ContentService.Save(doc);
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- //at this stage all property types were switched to invariant so even though the variant value
- //exists it will not be returned because the property type is invariant,
- //so this check proves that null will be returned
- Assert.IsNull(doc.GetValue("title", "en-US"));
-
- //we can now switch the property type to be variant and the value can be returned again
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
-
- }
-
- [Test]
- public void Change_Property_Type_From_Invariant_Variant()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Nothing;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.Name = "Home";
- doc.SetValue("title", "hello world");
- ServiceContext.ContentService.Save(doc);
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
-
- //change the property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
-
- //change back property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
- }
-
- [Test]
- public void Change_Property_Type_From_Variant_Invariant()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.SetCultureName("Home", "en-US");
- doc.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc);
-
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
-
- //change the property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
-
- //change back property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
- }
-
- [Test]
- public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //compose this from the other one
- var contentType2 = MockedContentTypes.CreateBasicContentType("test");
- contentType2.Variations = ContentVariation.Culture;
- contentType2.AddContentType(contentType);
- ServiceContext.ContentTypeService.Save(contentType2);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.SetCultureName("Home", "en-US");
- doc.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc);
-
- IContent doc2 = MockedContent.CreateBasicContent(contentType2);
- doc2.SetCultureName("Home", "en-US");
- doc2.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc2);
-
- //change the property type to be invariant
- contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
- doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
- Assert.AreEqual("hello world", doc2.GetValue("title"));
-
- //change back property type to be variant
- contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
- doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
- Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
- }
-
- [Test]
- public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
- {
- //create content type with a property type that varies by culture
- var contentType = MockedContentTypes.CreateBasicContentType();
- contentType.Variations = ContentVariation.Culture;
- var contentCollection = new PropertyTypeCollection(true);
- contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
- {
- Alias = "title",
- Name = "Title",
- Description = "",
- Mandatory = false,
- SortOrder = 1,
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- });
- contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
- ServiceContext.ContentTypeService.Save(contentType);
-
- //compose this from the other one
- var contentType2 = MockedContentTypes.CreateBasicContentType("test");
- contentType2.Variations = ContentVariation.Culture;
- contentType2.AddContentType(contentType);
- ServiceContext.ContentTypeService.Save(contentType2);
-
- //create some content of this content type
- IContent doc = MockedContent.CreateBasicContent(contentType);
- doc.SetCultureName("Home", "en-US");
- doc.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc);
-
- IContent doc2 = MockedContent.CreateBasicContent(contentType2);
- doc2.SetCultureName("Home", "en-US");
- doc2.SetValue("title", "hello world", "en-US");
- ServiceContext.ContentService.Save(doc2);
-
- //change the content type to be invariant
- contentType.Variations = ContentVariation.Nothing;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
- doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
-
- Assert.AreEqual("hello world", doc.GetValue("title"));
- Assert.AreEqual("hello world", doc2.GetValue("title"));
-
- //change back content type to be variant
- contentType.Variations = ContentVariation.Culture;
- ServiceContext.ContentTypeService.Save(contentType);
- doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
- doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
-
- //this will be null because the doc type was changed back to variant but it's property types don't get changed back
- Assert.IsNull(doc.GetValue("title", "en-US"));
- Assert.IsNull(doc2.GetValue("title", "en-US"));
- }
+
[Test]
public void Deleting_Content_Type_With_Hierarchy_Of_Content_Items_Moves_Orphaned_Content_To_Recycle_Bin()
diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
index 18ea95cd98..956de186be 100644
--- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
+++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs
@@ -17,6 +17,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
+using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.PublishedCache.NuCache;
@@ -106,44 +107,346 @@ namespace Umbraco.Tests.Services
}
}
+ [Test]
+ public void Change_Content_Type_Variation_Clears_Redirects()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Nothing;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+ var contentType2 = MockedContentTypes.CreateBasicContentType("test");
+ ServiceContext.ContentTypeService.Save(contentType2);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.Name = "Hello1";
+ ServiceContext.ContentService.Save(doc);
+
+ IContent doc2 = MockedContent.CreateBasicContent(contentType2);
+ ServiceContext.ContentService.Save(doc2);
+
+ ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
+ ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
+
+ Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
+ Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
+
+ //change variation
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
+ Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
+
+ }
+
+ [Test]
+ public void Change_Content_Type_From_Invariant_Variant()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Nothing;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.Name = "Hello1";
+ doc.SetValue("title", "hello world");
+ ServiceContext.ContentService.Save(doc);
+
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello1", doc.Name);
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsFalse (doc.IsCultureEdited("en-US"));
+
+ //change the content type to be variant, we will also update the name here to detect the copy changes
+ doc.Name = "Hello2";
+ ServiceContext.ContentService.Save(doc);
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello2", doc.GetCultureName("en-US"));
+ Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant
+ Assert.IsTrue(doc.Edited);
+ Assert.IsTrue(doc.IsCultureEdited("en-US"));
+
+ //change back property type to be invariant, we will also update the name here to detect the copy changes
+ doc.SetCultureName("Hello3", "en-US");
+ ServiceContext.ContentService.Save(doc);
+ contentType.Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello3", doc.Name);
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsFalse(doc.IsCultureEdited("en-US"));
+ }
+
+ [Test]
+ public void Change_Content_Type_From_Variant_Invariant()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Hello1", "en-US");
+ doc.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc);
+
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ Assert.AreEqual("Hello1", doc.GetCultureName("en-US"));
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsTrue(doc.IsCultureEdited("en-US"));
+
+ //change the content type to be invariant, we will also update the name here to detect the copy changes
+ doc.SetCultureName("Hello2", "en-US");
+ ServiceContext.ContentService.Save(doc);
+ contentType.Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello2", doc.Name);
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsFalse(doc.IsCultureEdited("en-US"));
+
+ //change back property type to be variant, we will also update the name here to detect the copy changes
+ doc.Name = "Hello3";
+ ServiceContext.ContentService.Save(doc);
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ //at this stage all property types were switched to invariant so even though the variant value
+ //exists it will not be returned because the property type is invariant,
+ //so this check proves that null will be returned
+ Assert.IsNull(doc.GetValue("title", "en-US"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsTrue(doc.IsCultureEdited("en-US")); // this is true because the name change is copied to the default language
+
+ //we can now switch the property type to be variant and the value can be returned again
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("Hello3", doc.GetCultureName("en-US"));
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ Assert.IsTrue(doc.Edited);
+ Assert.IsTrue(doc.IsCultureEdited("en-US"));
+
+ }
+
+
+ [Test]
+ public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Nothing;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //change the property type to be variant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+
+ //Cannot change a property type to be variant if the content type itself is not variant
+ Assert.Throws(() => ServiceContext.ContentTypeService.Save(contentType));
+ }
+
+ [Test]
+ public void Change_Property_Type_From_Invariant_Variant()
+ {
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Home", "en-US");
+ doc.SetValue("title", "hello world");
+ ServiceContext.ContentService.Save(doc);
+
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
+ Assert.IsTrue(doc.Edited);
+
+ //change the property type to be variant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ Assert.IsTrue(doc.IsCultureEdited("en-US"));
+ Assert.IsTrue(doc.Edited);
+
+ //change back property type to be invariant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang
+ Assert.IsTrue(doc.Edited);
+ }
+
+ [Test]
+ public void Change_Property_Type_From_Variant_Invariant()
+ {
+ //create content type with a property type that varies by culture
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Home", "en-US");
+ doc.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc);
+
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+
+ //change the property type to be invariant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+
+ //change back property type to be variant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ }
+
+ [Test]
+ public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
+ {
+ //create content type with a property type that varies by culture
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //compose this from the other one
+ var contentType2 = MockedContentTypes.CreateBasicContentType("test");
+ contentType2.Variations = ContentVariation.Culture;
+ contentType2.AddContentType(contentType);
+ ServiceContext.ContentTypeService.Save(contentType2);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Home", "en-US");
+ doc.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc);
+
+ IContent doc2 = MockedContent.CreateBasicContent(contentType2);
+ doc2.SetCultureName("Home", "en-US");
+ doc2.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc2);
+
+ //change the property type to be invariant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.AreEqual("hello world", doc2.GetValue("title"));
+
+ //change back property type to be variant
+ contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
+ Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
+ }
+
+ [Test]
+ public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
+ {
+ //create content type with a property type that varies by culture
+ var contentType = MockedContentTypes.CreateBasicContentType();
+ contentType.Variations = ContentVariation.Culture;
+ var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ //compose this from the other one
+ var contentType2 = MockedContentTypes.CreateBasicContentType("test");
+ contentType2.Variations = ContentVariation.Culture;
+ contentType2.AddContentType(contentType);
+ ServiceContext.ContentTypeService.Save(contentType2);
+
+ //create some content of this content type
+ IContent doc = MockedContent.CreateBasicContent(contentType);
+ doc.SetCultureName("Home", "en-US");
+ doc.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc);
+
+ IContent doc2 = MockedContent.CreateBasicContent(contentType2);
+ doc2.SetCultureName("Home", "en-US");
+ doc2.SetValue("title", "hello world", "en-US");
+ ServiceContext.ContentService.Save(doc2);
+
+ //change the content type to be invariant
+ contentType.Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
+
+ Assert.AreEqual("hello world", doc.GetValue("title"));
+ Assert.AreEqual("hello world", doc2.GetValue("title"));
+
+ //change back content type to be variant
+ contentType.Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+ doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
+ doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
+
+ //this will be null because the doc type was changed back to variant but it's property types don't get changed back
+ Assert.IsNull(doc.GetValue("title", "en-US"));
+ Assert.IsNull(doc2.GetValue("title", "en-US"));
+ }
+
[Test]
public void Change_Variations_SimpleContentType_VariantToInvariantAndBack()
{
// one simple content type, variant, with both variant and invariant properties
// can change it to invariant and back
- var languageEn = new Language("en") { IsDefault = true };
- ServiceContext.LocalizationService.Save(languageEn);
- var languageFr = new Language("fr");
- ServiceContext.LocalizationService.Save(languageFr);
+ CreateFrenchAndEnglishLangs();
- var contentType = new ContentType(-1)
- {
- Alias = "contentType",
- Name = "contentType",
- Variations = ContentVariation.Culture
- };
+ var contentType = CreateContentType(ContentVariation.Culture);
- var properties = new PropertyTypeCollection(true)
- {
- new PropertyType("value1", ValueStorageType.Ntext)
- {
- Alias = "value1",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value2", ValueStorageType.Ntext)
- {
- Alias = "value2",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties = CreatePropertyCollection(
+ ("value1", ContentVariation.Culture),
+ ("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
- var document = (IContent) new Content("document", -1, contentType);
+ var document = (IContent)new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en", "en");
@@ -226,28 +529,11 @@ namespace Umbraco.Tests.Services
var languageFr = new Language("fr");
ServiceContext.LocalizationService.Save(languageFr);
- var contentType = new ContentType(-1)
- {
- Alias = "contentType",
- Name = "contentType",
- Variations = ContentVariation.Nothing
- };
+ var contentType = CreateContentType(ContentVariation.Nothing);
- var properties = new PropertyTypeCollection(true)
- {
- new PropertyType("value1", ValueStorageType.Ntext)
- {
- Alias = "value1",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- },
- new PropertyType("value2", ValueStorageType.Ntext)
- {
- Alias = "value2",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties = CreatePropertyCollection(
+ ("value1", ContentVariation.Nothing),
+ ("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -326,33 +612,13 @@ namespace Umbraco.Tests.Services
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
- var languageEn = new Language("en") { IsDefault = true };
- ServiceContext.LocalizationService.Save(languageEn);
- var languageFr = new Language("fr");
- ServiceContext.LocalizationService.Save(languageFr);
+ CreateFrenchAndEnglishLangs();
- var contentType = new ContentType(-1)
- {
- Alias = "contentType",
- Name = "contentType",
- Variations = ContentVariation.Culture
- };
+ var contentType = CreateContentType(ContentVariation.Culture);
- var properties = new PropertyTypeCollection(true)
- {
- new PropertyType("value1", ValueStorageType.Ntext)
- {
- Alias = "value1",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value2", ValueStorageType.Ntext)
- {
- Alias = "value2",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties = CreatePropertyCollection(
+ ("value1", ContentVariation.Culture),
+ ("value2", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -429,6 +695,185 @@ namespace Umbraco.Tests.Services
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
}
+ [Test]
+ public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized()
+ {
+ // one simple content type, variant, with both variant and invariant properties
+ // can change an invariant property to variant and back
+
+ CreateFrenchAndEnglishLangs();
+
+ var contentType = CreateContentType(ContentVariation.Culture);
+
+ var properties = CreatePropertyCollection(("value1", ContentVariation.Culture));
+
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ var document = (IContent)new Content("document", -1, contentType);
+ document.SetCultureName("doc1en", "en");
+ document.SetCultureName("doc1fr", "fr");
+ document.SetValue("value1", "v1en-init", "en");
+ document.SetValue("value1", "v1fr-init", "fr");
+ ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.IsFalse(document.IsCultureEdited("en"));
+ Assert.IsFalse(document.IsCultureEdited("fr"));
+ Assert.IsFalse(document.Edited);
+
+ document.SetValue("value1", "v1en", "en"); //change the property culture value, so now this culture will be edited
+ document.SetValue("value1", "v1fr", "fr"); //change the property culture value, so now this culture will be edited
+ ServiceContext.ContentService.Save(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.AreEqual("v1en", document.GetValue("value1", "en"));
+ Assert.AreEqual("v1en-init", document.GetValue("value1", "en", published: true));
+ Assert.AreEqual("v1fr", document.GetValue("value1", "fr"));
+ Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true));
+ Assert.IsTrue(document.IsCultureEdited("en")); //This will be true because the edited value isn't the same as the published value
+ Assert.IsTrue(document.IsCultureEdited("fr")); //This will be true because the edited value isn't the same as the published value
+ Assert.IsTrue(document.Edited);
+
+ // switch property type to Invariant
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.IsTrue(document.IsCultureEdited("en")); //This will remain true because there is now a pending change for the invariant property data which is flagged under the default lang
+ Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
+ Assert.IsTrue(document.Edited);
+
+ //update the invariant value and publish
+ document.SetValue("value1", "v1inv");
+ ServiceContext.ContentService.SaveAndPublish(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", "en", published: true)); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
+ Assert.AreEqual("v1inv", document.GetValue("value1"));
+ Assert.AreEqual("v1inv", document.GetValue("value1", published: true));
+ Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
+ Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes
+ Assert.IsFalse(document.Edited);
+
+ // switch property back to Culture
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("v1inv", document.GetValue("value1", "en")); //The invariant property value gets copied over to the default language
+ Assert.AreEqual("v1inv", document.GetValue("value1", "en", published: true));
+ Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); //values are still retained
+ Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true)); //values are still retained
+ Assert.IsFalse(document.IsCultureEdited("en")); //The invariant published AND edited values are copied over to the default language
+ Assert.IsTrue(document.IsCultureEdited("fr")); //The previously existing french values are there and there is no published value
+ Assert.IsTrue(document.Edited); //Will be flagged edited again because the french culture had pending changes
+
+ // publish again
+ document.SetValue("value1", "v1en2", "en"); //update the value now that it's variant again
+ document.SetValue("value1", "v1fr2", "fr"); //update the value now that it's variant again
+ ServiceContext.ContentService.SaveAndPublish(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
+ Assert.AreEqual("v1fr2", document.GetValue("value1", "fr"));
+ Assert.IsNull(document.GetValue("value1")); //The value is there but the business logic returns null
+ Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the variant property value has been published
+ Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the variant property value has been published
+ Assert.IsFalse(document.Edited);
+ }
+
+ [Test]
+ public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized()
+ {
+ // one simple content type, variant, with both variant and invariant properties
+ // can change an invariant property to variant and back
+
+ CreateFrenchAndEnglishLangs();
+
+ var contentType = CreateContentType(ContentVariation.Culture);
+
+ var properties = CreatePropertyCollection(("value1", ContentVariation.Nothing));
+
+ contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ var document = (IContent)new Content("document", -1, contentType);
+ document.SetCultureName("doc1en", "en");
+ document.SetCultureName("doc1fr", "fr");
+ document.SetValue("value1", "v1en-init");
+ ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited'
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.IsFalse(document.IsCultureEdited("en"));
+ Assert.IsFalse(document.IsCultureEdited("fr"));
+ Assert.IsFalse(document.Edited);
+
+ document.SetValue("value1", "v1en"); //change the property value, so now the invariant (default) culture will be edited
+ ServiceContext.ContentService.Save(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.AreEqual("v1en", document.GetValue("value1"));
+ Assert.AreEqual("v1en-init", document.GetValue("value1", published: true));
+ Assert.IsTrue(document.IsCultureEdited("en")); //This is true because the invariant property reflects changes on the default lang
+ Assert.IsFalse(document.IsCultureEdited("fr"));
+ Assert.IsTrue(document.Edited);
+
+ // switch property type to Culture
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
+ ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.IsTrue(document.IsCultureEdited("en")); //Remains true
+ Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
+ Assert.IsTrue(document.Edited);
+
+ //update the culture value and publish
+ document.SetValue("value1", "v1en2", "en");
+ ServiceContext.ContentService.SaveAndPublish(document);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("doc1en", document.Name);
+ Assert.AreEqual("doc1en", document.GetCultureName("en"));
+ Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
+ Assert.IsNull(document.GetValue("value1")); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", published: true)); //The values are there but the business logic returns null
+ Assert.AreEqual("v1en2", document.GetValue("value1", "en"));
+ Assert.AreEqual("v1en2", document.GetValue("value1", "en", published: true));
+ Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published
+ Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited
+ Assert.IsFalse(document.Edited);
+
+ // switch property back to Invariant
+ contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
+ ServiceContext.ContentTypeService.Save(contentType);
+
+ document = ServiceContext.ContentService.GetById(document.Id);
+ Assert.AreEqual("v1en2", document.GetValue("value1")); //The variant property value gets copied over to the invariant
+ Assert.AreEqual("v1en2", document.GetValue("value1", published: true));
+ Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null
+ Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null
+ Assert.IsFalse(document.IsCultureEdited("en")); //The variant published AND edited values are copied over to the invariant
+ Assert.IsFalse(document.IsCultureEdited("fr"));
+ Assert.IsFalse(document.Edited);
+
+ }
+
[Test]
public void Change_Variations_ComposedContentType_1()
{
@@ -437,59 +882,22 @@ namespace Umbraco.Tests.Services
// can change the composing content type to invariant and back
// can change the composed content type to invariant and back
- var languageEn = new Language("en") { IsDefault = true };
- ServiceContext.LocalizationService.Save(languageEn);
- var languageFr = new Language("fr");
- ServiceContext.LocalizationService.Save(languageFr);
+ CreateFrenchAndEnglishLangs();
- var composing = new ContentType(-1)
- {
- Alias = "composing",
- Name = "composing",
- Variations = ContentVariation.Culture
- };
+ var composing = CreateContentType(ContentVariation.Culture, "composing");
- var properties1 = new PropertyTypeCollection(true)
- {
- new PropertyType("value11", ValueStorageType.Ntext)
- {
- Alias = "value11",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value12", ValueStorageType.Ntext)
- {
- Alias = "value12",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties1 = CreatePropertyCollection(
+ ("value11", ContentVariation.Culture),
+ ("value12", ContentVariation.Nothing));
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
ServiceContext.ContentTypeService.Save(composing);
- var composed = new ContentType(-1)
- {
- Alias = "composed",
- Name = "composed",
- Variations = ContentVariation.Culture
- };
+ var composed = CreateContentType(ContentVariation.Culture, "composed");
- var properties2 = new PropertyTypeCollection(true)
- {
- new PropertyType("value21", ValueStorageType.Ntext)
- {
- Alias = "value21",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value22", ValueStorageType.Ntext)
- {
- Alias = "value22",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties2 = CreatePropertyCollection(
+ ("value21", ContentVariation.Culture),
+ ("value22", ContentVariation.Nothing));
composed.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
composed.AddContentType(composing);
@@ -569,86 +977,32 @@ namespace Umbraco.Tests.Services
// can change the composing content type to invariant and back
// can change the variant composed content type to invariant and back
- var languageEn = new Language("en") { IsDefault = true };
- ServiceContext.LocalizationService.Save(languageEn);
- var languageFr = new Language("fr");
- ServiceContext.LocalizationService.Save(languageFr);
+ CreateFrenchAndEnglishLangs();
- var composing = new ContentType(-1)
- {
- Alias = "composing",
- Name = "composing",
- Variations = ContentVariation.Culture
- };
+ var composing = CreateContentType(ContentVariation.Culture, "composing");
- var properties1 = new PropertyTypeCollection(true)
- {
- new PropertyType("value11", ValueStorageType.Ntext)
- {
- Alias = "value11",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value12", ValueStorageType.Ntext)
- {
- Alias = "value12",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties1 = CreatePropertyCollection(
+ ("value11", ContentVariation.Culture),
+ ("value12", ContentVariation.Nothing));
composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" });
ServiceContext.ContentTypeService.Save(composing);
- var composed1 = new ContentType(-1)
- {
- Alias = "composed1",
- Name = "composed1",
- Variations = ContentVariation.Culture
- };
+ var composed1 = CreateContentType(ContentVariation.Culture, "composed1");
- var properties2 = new PropertyTypeCollection(true)
- {
- new PropertyType("value21", ValueStorageType.Ntext)
- {
- Alias = "value21",
- DataTypeId = -88,
- Variations = ContentVariation.Culture
- },
- new PropertyType("value22", ValueStorageType.Ntext)
- {
- Alias = "value22",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties2 = CreatePropertyCollection(
+ ("value21", ContentVariation.Culture),
+ ("value22", ContentVariation.Nothing));
composed1.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
composed1.AddContentType(composing);
ServiceContext.ContentTypeService.Save(composed1);
- var composed2 = new ContentType(-1)
- {
- Alias = "composed2",
- Name = "composed2",
- Variations = ContentVariation.Nothing
- };
+ var composed2 = CreateContentType(ContentVariation.Nothing, "composed2");
- var properties3 = new PropertyTypeCollection(true)
- {
- new PropertyType("value31", ValueStorageType.Ntext)
- {
- Alias = "value31",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- },
- new PropertyType("value32", ValueStorageType.Ntext)
- {
- Alias = "value32",
- DataTypeId = -88,
- Variations = ContentVariation.Nothing
- }
- };
+ var properties3 = CreatePropertyCollection(
+ ("value31", ContentVariation.Nothing),
+ ("value32", ContentVariation.Nothing));
composed2.PropertyGroups.Add(new PropertyGroup(properties3) { Name = "Content" });
composed2.AddContentType(composing);
@@ -754,5 +1108,35 @@ namespace Umbraco.Tests.Services
AssertJsonStartsWith(document2.Id,
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
}
+
+ private void CreateFrenchAndEnglishLangs()
+ {
+ var languageEn = new Language("en") { IsDefault = true };
+ ServiceContext.LocalizationService.Save(languageEn);
+ var languageFr = new Language("fr");
+ ServiceContext.LocalizationService.Save(languageFr);
+ }
+
+ private IContentType CreateContentType(ContentVariation variance, string alias = "contentType") => new ContentType(-1)
+ {
+ Alias = alias,
+ Name = alias,
+ Variations = variance
+ };
+
+ private PropertyTypeCollection CreatePropertyCollection(params (string alias, ContentVariation variance)[] props)
+ {
+ var propertyCollection = new PropertyTypeCollection(true);
+
+ foreach (var (alias, variance) in props)
+ propertyCollection.Add(new PropertyType(alias, ValueStorageType.Ntext)
+ {
+ Alias = alias,
+ DataTypeId = -88,
+ Variations = variance
+ });
+
+ return propertyCollection;
+ }
}
}
diff --git a/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs b/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs
index db7dd25152..9f72a022f3 100644
--- a/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs
+++ b/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Reflection;
using NUnit.Framework;
+using Umbraco.Core.Exceptions;
namespace Umbraco.Tests.Testing
{
@@ -29,7 +30,7 @@ namespace Umbraco.Tests.Testing
var methodName = test.MethodName;
var type = Type.GetType(typeName, true);
if (type == null)
- throw new Exception("panic"); // makes no sense
+ throw new PanicException($"Could not resolve the type from type name {typeName}"); // makes no sense
var methodInfo = type.GetMethod(methodName); // what about overloads?
var options = GetTestOptions(methodInfo);
return options;
@@ -53,7 +54,7 @@ namespace Umbraco.Tests.Testing
{
if (other == null) throw new ArgumentNullException(nameof(other));
if (!(Merge((TestOptionAttributeBase) other) is TOptions merged))
- throw new Exception("panic");
+ throw new PanicException("Could not merge test options");
return merged;
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
index a51afd200c..8dada26960 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js
@@ -47,6 +47,7 @@
function setSortingOptions() {
scope.sortableOptionsGroup = {
+ axis: 'y',
distance: 10,
tolerance: "pointer",
opacity: 0.7,
@@ -65,6 +66,7 @@
};
scope.sortableOptionsProperty = {
+ axis: 'y',
distance: 10,
tolerance: "pointer",
connectWith: ".umb-group-builder__properties",
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html
index 4af8c83983..60477ae9b8 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html
@@ -53,14 +53,14 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html
index e7513617b7..bc27196466 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html
@@ -138,7 +138,7 @@
id="{{login.authType}}" name="provider" value="{{login.authType}}"
title="Log in using your {{login.caption}} account">
- Sign in with {{login.caption}}
+ Sign in with {{login.caption}}
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index 147912ed41..c3d913be4c 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -348,6 +348,9 @@
8200/http://localhost:8200
+ 8120
+ /
+ http://localhost:8120FalseFalse
diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs
index fc029eabe4..528d5f6de5 100644
--- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs
+++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs
@@ -8,6 +8,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Core.Services;
+using Umbraco.Core.Exceptions;
namespace Umbraco.Web.Models.Mapping
{
@@ -577,7 +578,7 @@ namespace Umbraco.Web.Models.Mapping
udiType = Constants.UdiEntityType.DocumentType;
break;
default:
- throw new Exception("panic");
+ throw new PanicException($"Source is of type {source.GetType()} which isn't supported here");
}
return Udi.Create(udiType, source.Key);
diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs
index 1eb895075d..387fc670b8 100644
--- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs
@@ -2,6 +2,7 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
@@ -62,7 +63,7 @@ namespace Umbraco.Web.PropertyEditors
}
if (!(editorValue.DataTypeConfiguration is MultipleTextStringConfiguration config))
- throw new Exception("panic");
+ throw new PanicException($"editorValue.DataTypeConfiguration is {editorValue.DataTypeConfiguration.GetType()} but must be {typeof(MultipleTextStringConfiguration)}");
var max = config.Maximum;
//The legacy property editor saved this data as new line delimited! strange but we have to maintain that.
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
index 2d501fa3b5..8e2cf7bc3c 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using CSharpTest.Net.Collections;
using Umbraco.Core;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Scoping;
@@ -1044,7 +1045,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (_genObj == null)
_genObjs.Enqueue(_genObj = new GenObj(snapGen));
else if (_genObj.Gen != snapGen)
- throw new Exception("panic");
+ throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}");
}
else
{
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs
index c5b1df1206..9671949ff0 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Scoping;
using Umbraco.Web.PublishedCache.NuCache.Snap;
@@ -371,7 +372,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// if we have one already, ensure it's consistent
else if (_genObj.Gen != snapGen)
- throw new Exception("panic");
+ throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}");
}
else
{
diff --git a/src/Umbraco.Web/Search/BackgroundIndexRebuilder.cs b/src/Umbraco.Web/Search/BackgroundIndexRebuilder.cs
new file mode 100644
index 0000000000..d747d8d9e4
--- /dev/null
+++ b/src/Umbraco.Web/Search/BackgroundIndexRebuilder.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Threading;
+using Umbraco.Core.Logging;
+using Umbraco.Examine;
+using System.Threading.Tasks;
+using Umbraco.Core;
+using Umbraco.Web.Scheduling;
+
+namespace Umbraco.Web.Search
+{
+ ///
+ /// Utility to rebuild all indexes on a background thread
+ ///
+ public sealed class BackgroundIndexRebuilder
+ {
+ private static readonly object RebuildLocker = new object();
+ private readonly IndexRebuilder _indexRebuilder;
+ private readonly IMainDom _mainDom;
+ private readonly IProfilingLogger _logger;
+ private static BackgroundTaskRunner _rebuildOnStartupRunner;
+
+ public BackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IndexRebuilder indexRebuilder)
+ {
+ _mainDom = mainDom;
+ _logger = logger;
+ _indexRebuilder = indexRebuilder;
+ }
+
+ ///
+ /// Called to rebuild empty indexes on startup
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0)
+ {
+ // TODO: need a way to disable rebuilding on startup
+
+ lock (RebuildLocker)
+ {
+ if (_rebuildOnStartupRunner != null && _rebuildOnStartupRunner.IsRunning)
+ {
+ _logger.Warn("Call was made to RebuildIndexes but the task runner for rebuilding is already running");
+ return;
+ }
+
+ _logger.Info("Starting initialize async background thread.");
+ //do the rebuild on a managed background thread
+ var task = new RebuildOnStartupTask(_mainDom, _indexRebuilder, _logger, onlyEmptyIndexes, waitMilliseconds);
+
+ _rebuildOnStartupRunner = new BackgroundTaskRunner(
+ "RebuildIndexesOnStartup",
+ _logger);
+
+ _rebuildOnStartupRunner.TryAdd(task);
+ }
+ }
+
+ ///
+ /// Background task used to rebuild empty indexes on startup
+ ///
+ private class RebuildOnStartupTask : IBackgroundTask
+ {
+ private readonly IMainDom _mainDom;
+
+ private readonly IndexRebuilder _indexRebuilder;
+ private readonly ILogger _logger;
+ private readonly bool _onlyEmptyIndexes;
+ private readonly int _waitMilliseconds;
+
+ public RebuildOnStartupTask(IMainDom mainDom,
+ IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0)
+ {
+ _mainDom = mainDom;
+ _indexRebuilder = indexRebuilder ?? throw new ArgumentNullException(nameof(indexRebuilder));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _onlyEmptyIndexes = onlyEmptyIndexes;
+ _waitMilliseconds = waitMilliseconds;
+ }
+
+ public bool IsAsync => false;
+
+ public void Dispose()
+ {
+ }
+
+ public void Run()
+ {
+ try
+ {
+ // rebuilds indexes
+ RebuildIndexes();
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex, "Failed to rebuild empty indexes.");
+ }
+ }
+
+ public Task RunAsync(CancellationToken token)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Used to rebuild indexes on startup or cold boot
+ ///
+ private void RebuildIndexes()
+ {
+ //do not attempt to do this if this has been disabled since we are not the main dom.
+ //this can be called during a cold boot
+ if (!_mainDom.IsMainDom) return;
+
+ if (_waitMilliseconds > 0)
+ Thread.Sleep(_waitMilliseconds);
+
+ _indexRebuilder.ExamineManager.EnsureUnlocked(_mainDom, _logger);
+ _indexRebuilder.RebuildIndexes(_onlyEmptyIndexes);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs
index ed248a9e24..f34b1f862b 100644
--- a/src/Umbraco.Web/Search/ExamineComponent.cs
+++ b/src/Umbraco.Web/Search/ExamineComponent.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Threading;
using Examine;
using Umbraco.Core;
using Umbraco.Core.Cache;
@@ -15,42 +14,36 @@ using Umbraco.Core.Sync;
using Umbraco.Web.Cache;
using Umbraco.Examine;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
-using Umbraco.Web.Scheduling;
-using System.Threading.Tasks;
using Examine.LuceneEngine.Directories;
using Umbraco.Core.Composing;
+using System.ComponentModel;
namespace Umbraco.Web.Search
{
- public sealed class ExamineComponent : IComponent
+ public sealed class ExamineComponent : Umbraco.Core.Composing.IComponent
{
private readonly IExamineManager _examineManager;
private readonly IContentValueSetBuilder _contentValueSetBuilder;
private readonly IPublishedContentValueSetBuilder _publishedContentValueSetBuilder;
private readonly IValueSetBuilder _mediaValueSetBuilder;
private readonly IValueSetBuilder _memberValueSetBuilder;
- private static bool _disableExamineIndexing = false;
- private static bool _isConfigured = false;
- private static object _configuredInit = null;
private static object _isConfiguredLocker = new object();
private readonly IScopeProvider _scopeProvider;
- private readonly ServiceContext _services;
- private static BackgroundTaskRunner _rebuildOnStartupRunner;
- private static readonly object RebuildLocker = new object();
+ private readonly ServiceContext _services;
private readonly IMainDom _mainDom;
private readonly IProfilingLogger _logger;
private readonly IUmbracoIndexesCreator _indexCreator;
- private readonly IndexRebuilder _indexRebuilder;
+
// the default enlist priority is 100
// enlist with a lower priority to ensure that anything "default" runs after us
// but greater that SafeXmlReaderWriter priority which is 60
private const int EnlistPriority = 80;
-
+
public ExamineComponent(IMainDom mainDom,
IExamineManager examineManager, IProfilingLogger profilingLogger,
IScopeProvider scopeProvider, IUmbracoIndexesCreator indexCreator,
- IndexRebuilder indexRebuilder, ServiceContext services,
+ ServiceContext services,
IContentValueSetBuilder contentValueSetBuilder,
IPublishedContentValueSetBuilder publishedContentValueSetBuilder,
IValueSetBuilder mediaValueSetBuilder,
@@ -67,7 +60,6 @@ namespace Umbraco.Web.Search
_mainDom = mainDom;
_logger = profilingLogger;
_indexCreator = indexCreator;
- _indexRebuilder = indexRebuilder;
}
public void Initialize()
@@ -96,7 +88,6 @@ namespace Umbraco.Web.Search
//if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled!
Suspendable.ExamineEvents.SuspendIndexers(_logger);
- _disableExamineIndexing = true;
return; //exit, do not continue
}
@@ -117,70 +108,17 @@ namespace Umbraco.Web.Search
// bind to distributed cache events - this ensures that this logic occurs on ALL servers
// that are taking part in a load balanced environment.
ContentCacheRefresher.CacheUpdated += ContentCacheRefresherUpdated;
- ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated; ;
+ ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated;
MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated;
MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated;
-
- ConfigureIndexes(_logger, _examineManager);
-
- // TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start?
- RebuildIndexes(_indexRebuilder, _logger, true, 5000);
}
public void Terminate()
{ }
- ///
- /// Called to rebuild empty indexes on startup
- ///
- ///
- ///
- ///
- ///
- public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0)
- {
- // TODO: need a way to disable rebuilding on startup
-
- lock(RebuildLocker)
- {
- if (_rebuildOnStartupRunner != null && _rebuildOnStartupRunner.IsRunning)
- {
- logger.Warn("Call was made to RebuildIndexes but the task runner for rebuilding is already running");
- return;
- }
-
- logger.Info("Starting initialize async background thread.");
- //do the rebuild on a managed background thread
- var task = new RebuildOnStartupTask(indexRebuilder, logger, onlyEmptyIndexes, waitMilliseconds);
-
- _rebuildOnStartupRunner = new BackgroundTaskRunner(
- "RebuildIndexesOnStartup",
- logger);
-
- _rebuildOnStartupRunner.TryAdd(task);
- }
- }
-
- ///
- /// Called on startup to configure each index.
- ///
- ///
- /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before
- /// either of these happens, we need to configure the indexes.
- /// Configuring also ensure the indexes are not locked.
- ///
- private static void ConfigureIndexes(ILogger logger, IExamineManager examineManager)
- {
- LazyInitializer.EnsureInitialized(
- ref _configuredInit,
- ref _isConfigured,
- ref _isConfiguredLocker,
- () =>
- {
- examineManager.ConfigureLuceneIndexes(logger, _disableExamineIndexing);
- return null;
- });
- }
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("This method should not be used and will be removed in future versions, rebuilding indexes can be done with the IndexRebuilder or the BackgroundIndexRebuilder")]
+ public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) => Current.Factory.GetInstance().RebuildIndexes(onlyEmptyIndexes, waitMilliseconds);
#region Cache refresher updated event handlers
@@ -746,63 +684,6 @@ namespace Umbraco.Web.Search
}
#endregion
- ///
- /// Background task used to rebuild empty indexes on startup
- ///
- private class RebuildOnStartupTask : IBackgroundTask
- {
- private readonly IndexRebuilder _indexRebuilder;
- private readonly ILogger _logger;
- private readonly bool _onlyEmptyIndexes;
- private readonly int _waitMilliseconds;
-
- public RebuildOnStartupTask(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0)
- {
- _indexRebuilder = indexRebuilder ?? throw new ArgumentNullException(nameof(indexRebuilder));
- _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _onlyEmptyIndexes = onlyEmptyIndexes;
- _waitMilliseconds = waitMilliseconds;
- }
-
- public bool IsAsync => false;
-
- public void Dispose()
- {
- }
-
- public void Run()
- {
- try
- {
- // rebuilds indexes
- RebuildIndexes();
- }
- catch (Exception ex)
- {
- _logger.Error(ex, "Failed to rebuild empty indexes.");
- }
- }
-
- public Task RunAsync(CancellationToken token)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Used to rebuild indexes on startup or cold boot
- ///
- private void RebuildIndexes()
- {
- //do not attempt to do this if this has been disabled since we are not the main dom.
- //this can be called during a cold boot
- if (_disableExamineIndexing) return;
-
- if (_waitMilliseconds > 0)
- Thread.Sleep(_waitMilliseconds);
-
- ConfigureIndexes(_logger, _indexRebuilder.ExamineManager);
- _indexRebuilder.RebuildIndexes(_onlyEmptyIndexes);
- }
- }
+
}
}
diff --git a/src/Umbraco.Web/Search/ExamineComposer.cs b/src/Umbraco.Web/Search/ExamineComposer.cs
index 6e74f0e89d..0ade432d70 100644
--- a/src/Umbraco.Web/Search/ExamineComposer.cs
+++ b/src/Umbraco.Web/Search/ExamineComposer.cs
@@ -10,6 +10,7 @@ using Umbraco.Examine;
namespace Umbraco.Web.Search
{
+
///
/// Configures and installs Examine.
///
@@ -43,6 +44,7 @@ namespace Umbraco.Web.Search
false));
composition.RegisterUnique, MediaValueSetBuilder>();
composition.RegisterUnique, MemberValueSetBuilder>();
+ composition.RegisterUnique();
//We want to manage Examine's AppDomain shutdown sequence ourselves so first we'll disable Examine's default behavior
//and then we'll use MainDom to control Examine's shutdown - this MUST be done in Compose ie before ExamineManager
diff --git a/src/Umbraco.Web/Search/ExamineFinalComponent.cs b/src/Umbraco.Web/Search/ExamineFinalComponent.cs
new file mode 100644
index 0000000000..4514a78faa
--- /dev/null
+++ b/src/Umbraco.Web/Search/ExamineFinalComponent.cs
@@ -0,0 +1,42 @@
+using Examine;
+using Umbraco.Core.Logging;
+using Umbraco.Examine;
+using Umbraco.Core.Composing;
+using Umbraco.Core;
+
+namespace Umbraco.Web.Search
+{
+
+ ///
+ /// Executes after all other examine components have executed
+ ///
+ public sealed class ExamineFinalComponent : IComponent
+ {
+ private readonly IProfilingLogger _logger;
+ private readonly IExamineManager _examineManager;
+ BackgroundIndexRebuilder _indexRebuilder;
+ private readonly IMainDom _mainDom;
+
+ public ExamineFinalComponent(IProfilingLogger logger, IExamineManager examineManager, BackgroundIndexRebuilder indexRebuilder, IMainDom mainDom)
+ {
+ _logger = logger;
+ _examineManager = examineManager;
+ _indexRebuilder = indexRebuilder;
+ _mainDom = mainDom;
+ }
+
+ public void Initialize()
+ {
+ if (!_mainDom.IsMainDom) return;
+
+ _examineManager.EnsureUnlocked(_mainDom, _logger);
+
+ // TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start?
+ _indexRebuilder.RebuildIndexes(true, 5000);
+ }
+
+ public void Terminate()
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Search/ExamineFinalComposer.cs b/src/Umbraco.Web/Search/ExamineFinalComposer.cs
new file mode 100644
index 0000000000..5b6334f1f6
--- /dev/null
+++ b/src/Umbraco.Web/Search/ExamineFinalComposer.cs
@@ -0,0 +1,13 @@
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Web.Search
+{
+ // examine's final composer composes after all user composers
+ // and *also* after ICoreComposer (in case IUserComposer is disabled)
+ [ComposeAfter(typeof(IUserComposer))]
+ [ComposeAfter(typeof(ICoreComposer))]
+ [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
+ public class ExamineFinalComposer : ComponentComposer
+ { }
+}
diff --git a/src/Umbraco.Web/Search/ExamineUserComponent.cs b/src/Umbraco.Web/Search/ExamineUserComponent.cs
new file mode 100644
index 0000000000..35bc3f59ea
--- /dev/null
+++ b/src/Umbraco.Web/Search/ExamineUserComponent.cs
@@ -0,0 +1,37 @@
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Web.Search
+{
+ ///
+ /// An abstract class for custom index authors to inherit from
+ ///
+ public abstract class ExamineUserComponent : IComponent
+ {
+ private readonly IMainDom _mainDom;
+
+ public ExamineUserComponent(IMainDom mainDom)
+ {
+ _mainDom = mainDom;
+ }
+
+ ///
+ /// Initialize the component, eagerly exits if ExamineComponent.ExamineEnabled == false
+ ///
+ public void Initialize()
+ {
+ if (!_mainDom.IsMainDom) return;
+
+ InitializeComponent();
+ }
+
+ ///
+ /// Abstract method which executes to initialize this component if ExamineComponent.ExamineEnabled == true
+ ///
+ protected abstract void InitializeComponent();
+
+ public virtual void Terminate()
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs b/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs
index 560fb19f09..cb25e1242a 100644
--- a/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs
+++ b/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs
@@ -8,6 +8,7 @@ using Umbraco.Examine;
namespace Umbraco.Web.Search
{
+
///
/// Used to return diagnostic data for any index
///
diff --git a/src/Umbraco.Web/Suspendable.cs b/src/Umbraco.Web/Suspendable.cs
index 86c83120ea..cfe4d28e8b 100644
--- a/src/Umbraco.Web/Suspendable.cs
+++ b/src/Umbraco.Web/Suspendable.cs
@@ -50,6 +50,8 @@ namespace Umbraco.Web
}
}
+ //This is really needed at all since the only place this is used is in ExamineComponent and that already maintains a flag of whether it suspsended or not
+ // AHH... but Deploy probably uses this?
public static class ExamineEvents
{
private static bool _tried, _suspended;
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 730a321a16..dc06222209 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -233,6 +233,10 @@
+
+
+
+