Merge pull request #3155 from umbraco/temp8-222-variant-invariant-data-updates
variant to invariant data updates
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
|
||||
//TODO: This should just exist in the content service/repo!
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
||||
|
||||
@@ -363,22 +363,28 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="propertyTypeAlias">Alias of the <see cref="PropertyType"/> to remove</param>
|
||||
public void RemovePropertyType(string propertyTypeAlias)
|
||||
{
|
||||
//check if the property exist in one of our collections
|
||||
if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias))
|
||||
|| _propertyTypes.Any(x => x.Alias == propertyTypeAlias))
|
||||
{
|
||||
//set the flag that a property has been removed
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
}
|
||||
|
||||
//check through each property group to see if we can remove the property type by alias from it
|
||||
foreach (var propertyGroup in PropertyGroups)
|
||||
{
|
||||
propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias);
|
||||
if (propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias))
|
||||
{
|
||||
if (!HasPropertyTypeBeenRemoved)
|
||||
{
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias))
|
||||
//check through each local property type collection (not assigned to a tab)
|
||||
if (_propertyTypes.RemoveItem(propertyTypeAlias))
|
||||
{
|
||||
_propertyTypes.RemoveItem(propertyTypeAlias);
|
||||
if (!HasPropertyTypeBeenRemoved)
|
||||
{
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,23 +414,9 @@ namespace Umbraco.Core.Models
|
||||
/// PropertyTypes that are not part of a PropertyGroup
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
//fixme should we mark this as EditorBrowsable hidden since it really isn't ever used?
|
||||
internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a specific property on the current <see cref="IContent"/> entity is dirty.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property to check</param>
|
||||
/// <returns>True if Property is dirty, otherwise False</returns>
|
||||
public override bool IsPropertyDirty(string propertyName)
|
||||
{
|
||||
bool existsInEntity = base.IsPropertyDirty(propertyName);
|
||||
|
||||
bool anyDirtyGroups = PropertyGroups.Any(x => x.IsPropertyDirty(propertyName));
|
||||
bool anyDirtyTypes = PropertyTypes.Any(x => x.IsPropertyDirty(propertyName));
|
||||
|
||||
return existsInEntity || anyDirtyGroups || anyDirtyTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current entity is dirty.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -16,5 +20,63 @@ namespace Umbraco.Core.Models
|
||||
else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member;
|
||||
return itemType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to check if any property type was changed between variant/invariant
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType)
|
||||
{
|
||||
return contentType.WasPropertyTypeVariationChanged(out var _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to check if any property type was changed between variant/invariant
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, out IReadOnlyCollection<string> aliases)
|
||||
{
|
||||
var a = new List<string>();
|
||||
|
||||
// property variation change?
|
||||
var hasAnyPropertyVariationChanged = contentType.PropertyTypes.Any(propertyType =>
|
||||
{
|
||||
if (!(propertyType is IRememberBeingDirty dirtyProperty))
|
||||
throw new Exception("oops");
|
||||
|
||||
// skip new properties
|
||||
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
|
||||
var isNewProperty = dirtyProperty.WasPropertyDirty("Id");
|
||||
if (isNewProperty) return false;
|
||||
|
||||
// variation change?
|
||||
var dirty = dirtyProperty.WasPropertyDirty("Variations");
|
||||
if (dirty)
|
||||
a.Add(propertyType.Alias);
|
||||
|
||||
return dirty;
|
||||
|
||||
});
|
||||
|
||||
aliases = a;
|
||||
return hasAnyPropertyVariationChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of content types the composition is used in
|
||||
/// </summary>
|
||||
/// <param name="allContentTypes"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<IContentTypeComposition> GetWhereCompositionIsUsedInContentTypes(this IContentTypeComposition source,
|
||||
IContentTypeComposition[] allContentTypes)
|
||||
{
|
||||
var sourceId = source != null ? source.Id : 0;
|
||||
|
||||
// find which content types are using this composition
|
||||
return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets or sets the content types that compose this content type.
|
||||
/// </summary>
|
||||
//fixme: we should be storing key references, not the object else we are caching way too much
|
||||
IEnumerable<IContentTypeComposition> ContentTypeComposition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -124,10 +124,11 @@ namespace Umbraco.Core.Models
|
||||
return this.Any(x => x.Alias == propertyAlias);
|
||||
}
|
||||
|
||||
public void RemoveItem(string propertyTypeAlias)
|
||||
public bool RemoveItem(string propertyTypeAlias)
|
||||
{
|
||||
var key = IndexOfKey(propertyTypeAlias);
|
||||
if (key != -1) RemoveItem(key);
|
||||
return key != -1;
|
||||
}
|
||||
|
||||
public int IndexOfKey(string key)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
[ExplicitColumns]
|
||||
internal class PropertyDataDto
|
||||
{
|
||||
private const string TableName = Constants.DatabaseSchema.Tables.PropertyData;
|
||||
public const string TableName = Constants.DatabaseSchema.Tables.PropertyData;
|
||||
public const int VarcharLength = 512;
|
||||
public const int SegmentLength = 256;
|
||||
|
||||
|
||||
@@ -1036,7 +1036,7 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
var pd = sql.SqlContext.PocoDataFactory.ForType(typeof (TDto));
|
||||
var tableName = tableAlias ?? pd.TableInfo.TableName;
|
||||
var queryColumns = pd.QueryColumns;
|
||||
var queryColumns = pd.QueryColumns.ToList();
|
||||
|
||||
Dictionary<string, string> aliases = null;
|
||||
|
||||
@@ -1056,7 +1056,11 @@ namespace Umbraco.Core.Persistence
|
||||
return fieldName;
|
||||
}).ToArray();
|
||||
|
||||
queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToArray();
|
||||
//only get the columns that exist in the selected names
|
||||
queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToList();
|
||||
|
||||
//ensure the order of the columns in the expressions is the order in the result
|
||||
queryColumns.Sort((a, b) => names.IndexOf(a.Key).CompareTo(names.IndexOf(b.Key)));
|
||||
}
|
||||
|
||||
string GetAlias(PocoColumn column)
|
||||
|
||||
@@ -10,6 +10,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
TItem Get(string alias);
|
||||
IEnumerable<MoveEventInfo<TItem>> Move(TItem moving, EntityContainer container);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the content types that are direct compositions of the content type
|
||||
/// </summary>
|
||||
/// <param name="id">The content type id</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TItem> GetTypesDirectlyComposedOf(int id);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -608,6 +608,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
#region UnitOfWork Events
|
||||
|
||||
//fixme: The reason these events are in the repository is for legacy, the events should exist at the service
|
||||
// level now since we can fire these events within the transaction... so move the events to service level
|
||||
|
||||
public class ScopedEntityEventArgs : EventArgs
|
||||
{
|
||||
public ScopedEntityEventArgs(IScope scope, TEntity entity)
|
||||
|
||||
@@ -274,6 +274,8 @@ AND umbracoNode.id <> @id",
|
||||
if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null &&
|
||||
compositionBase.RemovedContentTypeKeyTracker.Any())
|
||||
{
|
||||
//TODO: Could we do the below with bulk SQL statements instead of looking everything up and then manipulating?
|
||||
|
||||
// find Content based on the current ContentType
|
||||
var sql = Sql()
|
||||
.SelectAll()
|
||||
@@ -292,6 +294,7 @@ AND umbracoNode.id <> @id",
|
||||
// based on the PropertyTypes that belong to the removed ContentType.
|
||||
foreach (var contentDto in contentDtos)
|
||||
{
|
||||
//TODO: This could be done with bulk SQL statements
|
||||
foreach (var propertyType in propertyTypes)
|
||||
{
|
||||
var nodeId = contentDto.NodeId;
|
||||
@@ -323,9 +326,7 @@ AND umbracoNode.id <> @id",
|
||||
});
|
||||
}
|
||||
|
||||
// fixme below, manage the property type
|
||||
|
||||
// delete ??? fixme wtf is this?
|
||||
// delete property types
|
||||
// ... by excepting entries from db with entries from collections
|
||||
if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty()))
|
||||
{
|
||||
@@ -404,10 +405,49 @@ AND umbracoNode.id <> @id",
|
||||
propertyType.PropertyGroupId = new Lazy<int>(() => groupId);
|
||||
}
|
||||
|
||||
//check if the content type variation has been changed
|
||||
var ctVariationChanging = entity.IsPropertyDirty("Variations");
|
||||
if (ctVariationChanging)
|
||||
{
|
||||
//we've already looked up the previous version of the content type so we know it's previous variation state
|
||||
MoveVariantData(entity, (ContentVariation)dtoPk.Variations, entity.Variations);
|
||||
Clear301Redirects(entity);
|
||||
ClearScheduledPublishing(entity);
|
||||
}
|
||||
|
||||
//track any content type/property types that are changing variation which will require content updates
|
||||
var propertyTypeVariationChanges = new Dictionary<int, ContentVariation>();
|
||||
|
||||
// insert or update properties
|
||||
// all of them, no-group and in-groups
|
||||
foreach (var propertyType in entity.PropertyTypes)
|
||||
{
|
||||
//if the content type variation isn't changing track if any property type is changing
|
||||
if (!ctVariationChanging)
|
||||
{
|
||||
if (propertyType.IsPropertyDirty("Variations"))
|
||||
{
|
||||
propertyTypeVariationChanges[propertyType.Id] = propertyType.Variations;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(entity.Variations)
|
||||
{
|
||||
case ContentVariation.Nothing:
|
||||
//if the content type is changing to Nothing, then all property type's must change to nothing
|
||||
propertyType.Variations = ContentVariation.Nothing;
|
||||
break;
|
||||
case ContentVariation.Culture:
|
||||
//we don't need to modify the property type in this case
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); //TODO: Support this
|
||||
}
|
||||
}
|
||||
|
||||
var groupId = propertyType.PropertyGroupId?.Value ?? default(int);
|
||||
// if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias
|
||||
if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default(int))
|
||||
@@ -431,6 +471,28 @@ AND umbracoNode.id <> @id",
|
||||
orphanPropertyTypeIds.Remove(typeId);
|
||||
}
|
||||
|
||||
//check if any property types were changing variation
|
||||
if (propertyTypeVariationChanges.Count > 0)
|
||||
{
|
||||
var changes = new Dictionary<int, (ContentVariation, ContentVariation)>();
|
||||
|
||||
//now get the current property type variations for the changed ones so that we know which variation they
|
||||
//are going from and to
|
||||
var from = Database.Dictionary<int, byte>(Sql()
|
||||
.Select<PropertyTypeDto>(x => x.Id, x => x.Variations)
|
||||
.From<PropertyTypeDto>()
|
||||
.WhereIn<PropertyTypeDto>(x => x.Id, propertyTypeVariationChanges.Keys));
|
||||
|
||||
foreach (var f in from)
|
||||
{
|
||||
changes[f.Key] = (propertyTypeVariationChanges[f.Key], (ContentVariation)f.Value);
|
||||
}
|
||||
|
||||
//perform the move
|
||||
MoveVariantData(changes);
|
||||
}
|
||||
|
||||
|
||||
// deal with orphan properties: those that were in a deleted tab,
|
||||
// and have not been re-mapped to another tab or to 'generic properties'
|
||||
if (orphanPropertyTypeIds != null)
|
||||
@@ -438,6 +500,221 @@ AND umbracoNode.id <> @id",
|
||||
DeletePropertyType(entity.Id, id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear any redirects associated with content for a content type
|
||||
/// </summary>
|
||||
private void Clear301Redirects(IContentTypeComposition contentType)
|
||||
{
|
||||
//first clear out any existing property data that might already exists under the default lang
|
||||
var sqlSelect = Sql().Select<NodeDto>(x => x.UniqueId)
|
||||
.From<NodeDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
var sqlDelete = Sql()
|
||||
.Delete<RedirectUrlDto>()
|
||||
.WhereIn((System.Linq.Expressions.Expression<Func<RedirectUrlDto, object>>)(x => x.ContentKey), sqlSelect);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear any scheduled publishing associated with content for a content type
|
||||
/// </summary>
|
||||
private void ClearScheduledPublishing(IContentTypeComposition contentType)
|
||||
{
|
||||
//TODO: Fill this in when scheduled publishing is enabled for variants
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves variant data for property type changes
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeChanges"></param>
|
||||
private void MoveVariantData(IDictionary<int, (ContentVariation, ContentVariation)> propertyTypeChanges)
|
||||
{
|
||||
var defaultLangId = Database.First<int>(Sql().Select<LanguageDto>(x => x.Id).From<LanguageDto>().Where<LanguageDto>(x => x.IsDefault));
|
||||
|
||||
//Group by the "To" variation so we can bulk update in the correct batches
|
||||
foreach(var g in propertyTypeChanges.GroupBy(x => x.Value.Item2))
|
||||
{
|
||||
var propertyTypeIds = g.Select(s => s.Key).ToList();
|
||||
|
||||
//the ContentVariation that the data is moving "To"
|
||||
var toVariantType = g.Key;
|
||||
|
||||
switch(toVariantType)
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
MovePropertyDataToVariantCulture(defaultLangId, propertyTypeIds: propertyTypeIds);
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
MovePropertyDataToVariantNothing(defaultLangId, propertyTypeIds: propertyTypeIds);
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); //TODO: Support this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves variant data for a content type variation change
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="to"></param>
|
||||
private void MoveVariantData(IContentTypeComposition contentType, ContentVariation from, ContentVariation to)
|
||||
{
|
||||
var defaultLangId = Database.First<int>(Sql().Select<LanguageDto>(x => x.Id).From<LanguageDto>().Where<LanguageDto>(x => x.IsDefault));
|
||||
|
||||
var sqlPropertyTypeIds = Sql().Select<PropertyTypeDto>(x => x.Id).From<PropertyTypeDto>().Where<PropertyTypeDto>(x => x.ContentTypeId == contentType.Id);
|
||||
switch (to)
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
//move the property data
|
||||
|
||||
MovePropertyDataToVariantCulture(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds);
|
||||
|
||||
//now we need to move the names
|
||||
//first clear out any existing names that might already exists under the default lang
|
||||
//there's 2x tables to update
|
||||
|
||||
//clear out the versionCultureVariation table
|
||||
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
|
||||
.From<ContentVersionCultureVariationDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
|
||||
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLangId);
|
||||
var sqlDelete = Sql()
|
||||
.Delete<ContentVersionCultureVariationDto>()
|
||||
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//clear out the documentCultureVariation table
|
||||
sqlSelect = Sql().Select<DocumentCultureVariationDto>(x => x.Id)
|
||||
.From<DocumentCultureVariationDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, DocumentCultureVariationDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
|
||||
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLangId);
|
||||
sqlDelete = Sql()
|
||||
.Delete<DocumentCultureVariationDto>()
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//now we need to insert names into these 2 tables based on the invariant data
|
||||
|
||||
//insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
|
||||
var cols = Sql().Columns<ContentVersionCultureVariationDto>(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
|
||||
sqlSelect = Sql().Select<ContentVersionDto>(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
|
||||
.Append($", {defaultLangId}") //default language ID
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
|
||||
Database.Execute(sqlInsert);
|
||||
|
||||
//insert rows into the documentCultureVariation table
|
||||
cols = Sql().Columns<DocumentCultureVariationDto>(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
|
||||
sqlSelect = Sql().Select<DocumentDto>(x => x.NodeId, x => x.Edited, x => x.Published)
|
||||
.AndSelect<NodeDto>(x => x.Text)
|
||||
.Append($", 1, {defaultLangId}") //make Available + default language ID
|
||||
.From<DocumentDto>()
|
||||
.InnerJoin<NodeDto>().On<NodeDto, DocumentDto>(x => x.NodeId, x => x.NodeId)
|
||||
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
|
||||
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
|
||||
sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
|
||||
Database.Execute(sqlInsert);
|
||||
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
//move the property data
|
||||
|
||||
MovePropertyDataToVariantNothing(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds);
|
||||
|
||||
//we dont need to move the names! this is because we always keep the invariant names with the name of the default language.
|
||||
|
||||
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
|
||||
// if we want these SQL statements back, look into GIT history
|
||||
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
default:
|
||||
throw new NotSupportedException(); //TODO: Support this
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will move all property data from variant to invariant
|
||||
/// </summary>
|
||||
/// <param name="defaultLangId"></param>
|
||||
/// <param name="propertyTypeIds">Optional list of property type ids of the properties to be updated</param>
|
||||
/// <param name="sqlPropertyTypeIds">Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated</param>
|
||||
private void MovePropertyDataToVariantNothing(int defaultLangId, IReadOnlyCollection<int> propertyTypeIds = null, Sql<ISqlContext> sqlPropertyTypeIds = null)
|
||||
{
|
||||
//first clear out any existing property data that might already exists under the default lang
|
||||
var sqlDelete = Sql()
|
||||
.Delete<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
if (sqlPropertyTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, sqlPropertyTypeIds);
|
||||
if (propertyTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//now insert all property data into the default language that exists under the invariant lang
|
||||
var cols = Sql().Columns<PropertyDataDto>(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId);
|
||||
var sqlSelectData = Sql().Select<PropertyDataDto>(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue)
|
||||
.Append(", NULL") //null language ID
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == defaultLangId);
|
||||
if (sqlPropertyTypeIds != null)
|
||||
sqlSelectData.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, sqlPropertyTypeIds);
|
||||
if (propertyTypeIds != null)
|
||||
sqlSelectData.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData);
|
||||
|
||||
Database.Execute(sqlInsert);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will move all property data from invariant to variant
|
||||
/// </summary>
|
||||
/// <param name="defaultLangId"></param>
|
||||
/// <param name="propertyTypeIds">Optional list of property type ids of the properties to be updated</param>
|
||||
/// <param name="sqlPropertyTypeIds">Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated</param>
|
||||
private void MovePropertyDataToVariantCulture(int defaultLangId, IReadOnlyCollection<int> propertyTypeIds = null, Sql<ISqlContext> sqlPropertyTypeIds = null)
|
||||
{
|
||||
//first clear out any existing property data that might already exists under the default lang
|
||||
var sqlDelete = Sql()
|
||||
.Delete<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == defaultLangId);
|
||||
if (sqlPropertyTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, sqlPropertyTypeIds);
|
||||
if (propertyTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//now insert all property data into the default language that exists under the invariant lang
|
||||
var cols = Sql().Columns<PropertyDataDto>(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId);
|
||||
var sqlSelectData = Sql().Select<PropertyDataDto>(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue)
|
||||
.Append($", {defaultLangId}") //default language ID
|
||||
.From<PropertyDataDto>()
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
if (sqlPropertyTypeIds != null)
|
||||
sqlSelectData.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, sqlPropertyTypeIds);
|
||||
if (propertyTypeIds != null)
|
||||
sqlSelectData.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData);
|
||||
|
||||
Database.Execute(sqlInsert);
|
||||
}
|
||||
|
||||
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
|
||||
{
|
||||
// first clear dependencies
|
||||
@@ -571,8 +848,11 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TEntity> GetTypesDirectlyComposedOf(int id)
|
||||
{
|
||||
//fixme - this will probably be more efficient to simply load all content types and do the calculation, see GetWhereCompositionIsUsedInContentTypes
|
||||
|
||||
var sql = Sql()
|
||||
.SelectAll()
|
||||
.From<NodeDto>()
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return new FullDataSetRepositoryCachePolicy<ILanguage, int>(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false);
|
||||
}
|
||||
|
||||
private FullDataSetRepositoryCachePolicy<ILanguage, int> TypedCachePolicy => (FullDataSetRepositoryCachePolicy<ILanguage, int>) CachePolicy;
|
||||
private FullDataSetRepositoryCachePolicy<ILanguage, int> TypedCachePolicy => CachePolicy as FullDataSetRepositoryCachePolicy<ILanguage, int>;
|
||||
|
||||
#region Overrides of RepositoryBase<int,Language>
|
||||
|
||||
@@ -225,7 +225,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public ILanguage GetByIsoCode(string isoCode)
|
||||
{
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
// ensure cache is populated, in a non-expensive way
|
||||
if (TypedCachePolicy != null)
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll);
|
||||
|
||||
|
||||
var id = GetIdByIsoCode(isoCode, throwOnNotFound: false);
|
||||
return id.HasValue ? Get(id.Value) : null;
|
||||
}
|
||||
@@ -238,7 +242,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
if (isoCode == null) return null;
|
||||
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
// ensure cache is populated, in a non-expensive way
|
||||
if (TypedCachePolicy != null)
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll);
|
||||
else
|
||||
PerformGetAll(); //we don't have a typed cache (i.e. unit tests) but need to populate the _codeIdMap
|
||||
|
||||
lock (_codeIdMap)
|
||||
{
|
||||
if (_codeIdMap.TryGetValue(isoCode, out var id)) return id;
|
||||
@@ -256,7 +265,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
if (id == null) return null;
|
||||
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
// ensure cache is populated, in a non-expensive way
|
||||
if (TypedCachePolicy != null)
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll);
|
||||
else
|
||||
PerformGetAll();
|
||||
|
||||
lock (_codeIdMap) // yes, we want to lock _codeIdMap
|
||||
{
|
||||
if (_idCodeMap.TryGetValue(id.Value, out var isoCode)) return isoCode;
|
||||
@@ -279,8 +293,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// do NOT leak that language, it's not deep-cloned!
|
||||
private ILanguage GetDefault()
|
||||
{
|
||||
// get all cached, non-cloned
|
||||
var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList();
|
||||
// get all cached
|
||||
var languages = (TypedCachePolicy?.GetAllCached(PerformGetAll) //try to get all cached non-cloned if using the correct cache policy (not the case in unit tests)
|
||||
?? CachePolicy.GetAll(Array.Empty<int>(), PerformGetAll)).ToList();
|
||||
|
||||
var language = languages.FirstOrDefault(x => x.IsDefault);
|
||||
if (language != null) return language;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
|
||||
{
|
||||
var asString = value.ToString();
|
||||
var asString = value == null ? "" : value.ToString();
|
||||
|
||||
var emailVal = new EmailAddressAttribute();
|
||||
|
||||
|
||||
@@ -109,23 +109,7 @@ namespace Umbraco.Core.Services
|
||||
|
||||
return new ContentTypeAvailableCompositionsResults(ancestors, result);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the list of content types the composition is used in
|
||||
/// </summary>
|
||||
/// <param name="allContentTypes"></param>
|
||||
/// <param name="ctService"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<IContentTypeComposition> GetWhereCompositionIsUsedInContentTypes(this IContentTypeService ctService,
|
||||
IContentTypeComposition source,
|
||||
IContentTypeComposition[] allContentTypes)
|
||||
{
|
||||
|
||||
var sourceId = source != null ? source.Id : 0;
|
||||
|
||||
// find which content types are using this composition
|
||||
return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray();
|
||||
}
|
||||
|
||||
|
||||
private static IContentTypeComposition[] GetAncestors(IContentTypeComposition ctype, IContentTypeComposition[] allContentTypes)
|
||||
{
|
||||
|
||||
@@ -20,12 +20,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
internal static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> Changed;
|
||||
|
||||
// that one is always immediate (transactional)
|
||||
public static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> UowRefreshedEntity;
|
||||
public static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> ScopedRefreshedEntity;
|
||||
|
||||
// used by tests to clear events
|
||||
internal static void ClearScopeEvents()
|
||||
{
|
||||
UowRefreshedEntity = null;
|
||||
ScopedRefreshedEntity = null;
|
||||
}
|
||||
|
||||
// these must be dispatched
|
||||
@@ -48,7 +48,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
protected void OnUowRefreshedEntity(ContentTypeChange<TItem>.EventArgs args)
|
||||
{
|
||||
// that one is always immediate (not dispatched, transactional)
|
||||
UowRefreshedEntity.RaiseEvent(args, This);
|
||||
ScopedRefreshedEntity.RaiseEvent(args, This);
|
||||
}
|
||||
|
||||
protected bool OnSavingCancelled(IScope scope, SaveEventArgs<TItem> args)
|
||||
|
||||
@@ -118,6 +118,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
// - content type alias changed
|
||||
// - content type property removed, or alias changed
|
||||
// - content type composition removed (not testing if composition had properties...)
|
||||
// - content type variation changed
|
||||
// - property type variation changed
|
||||
//
|
||||
// because these are the changes that would impact the raw content data
|
||||
|
||||
@@ -132,7 +134,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
var dirty = (IRememberBeingDirty)contentType;
|
||||
|
||||
// skip new content types
|
||||
var isNewContentType = dirty.WasPropertyDirty("HasIdentity");
|
||||
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
|
||||
var isNewContentType = dirty.WasPropertyDirty("Id");
|
||||
if (isNewContentType)
|
||||
{
|
||||
AddChange(changes, contentType, ContentTypeChangeTypes.Create);
|
||||
@@ -149,12 +152,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
throw new Exception("oops");
|
||||
|
||||
// skip new properties
|
||||
var isNewProperty = dirtyProperty.WasPropertyDirty("HasIdentity");
|
||||
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
|
||||
var isNewProperty = dirtyProperty.WasPropertyDirty("Id");
|
||||
if (isNewProperty) return false;
|
||||
|
||||
// alias change?
|
||||
var hasPropertyAliasBeenChanged = dirtyProperty.WasPropertyDirty("Alias");
|
||||
return hasPropertyAliasBeenChanged;
|
||||
return dirtyProperty.WasPropertyDirty("Alias");
|
||||
});
|
||||
|
||||
// removed properties?
|
||||
@@ -163,8 +166,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
// removed compositions?
|
||||
var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved");
|
||||
|
||||
// variation changed?
|
||||
var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations");
|
||||
|
||||
// property variation change?
|
||||
var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged();
|
||||
|
||||
// main impact on properties?
|
||||
var hasPropertyMainImpact = hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias;
|
||||
var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged
|
||||
|| hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias;
|
||||
|
||||
if (hasAliasChanged || hasPropertyMainImpact)
|
||||
{
|
||||
@@ -336,6 +346,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
public IEnumerable<TItem> GetComposedOf(int id)
|
||||
{
|
||||
//fixme: this is essentially the same as ContentTypeServiceExtensions.GetWhereCompositionIsUsedInContentTypes which loads
|
||||
// all content types to figure this out, this instead makes quite a few queries so should be replaced
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
|
||||
@@ -63,8 +63,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
|
||||
//TODO Add test to verify SetDefaultTemplates updates both AllowedTemplates and DefaultTemplate(id).
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void Maps_Templates_Correctly()
|
||||
{
|
||||
@@ -377,7 +376,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
repository.Save(contentType);
|
||||
|
||||
|
||||
var dirty = ((ICanBeDirty)contentType).IsDirty();
|
||||
var dirty = contentType.IsDirty();
|
||||
|
||||
// Assert
|
||||
Assert.That(contentType.HasIdentity, Is.True);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public class ContentRepositoryTest : TestWithDatabaseBase
|
||||
public class DocumentRepositoryTest : TestWithDatabaseBase
|
||||
{
|
||||
public override void SetUp()
|
||||
{
|
||||
@@ -15,6 +15,7 @@ using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Core.Components;
|
||||
|
||||
namespace Umbraco.Tests.Services
|
||||
{
|
||||
@@ -23,6 +24,361 @@ namespace Umbraco.Tests.Services
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)]
|
||||
public class ContentTypeServiceTests : TestWithSomeContentBase
|
||||
{
|
||||
[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_Media_Type_With_Hierarchy_Of_Media_Items_Moves_Orphaned_Media_To_Recycle_Bin()
|
||||
{
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
<Compile Include="Migrations\MigrationTests.cs" />
|
||||
<Compile Include="Models\PathValidationTests.cs" />
|
||||
<Compile Include="Models\VariationTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
|
||||
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
|
||||
@@ -391,7 +392,6 @@
|
||||
<Compile Include="Persistence\Mappers\RelationTypeMapperTest.cs" />
|
||||
<Compile Include="Persistence\NPocoTests\NPocoSqlTests.cs" />
|
||||
<Compile Include="Persistence\Querying\QueryBuilderTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\ContentRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\ContentTypeRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\DataTypeDefinitionRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\DictionaryRepositoryTest.cs" />
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
|
||||
evts.push(eventsService.on("editors.documentType.saved", function (name, args) {
|
||||
// if this content item uses the updated doc type we need to reload the content item
|
||||
if (args && args.documentType && args.documentType.key === content.documentType.key) {
|
||||
if (args && args.documentType && args.documentType.key === $scope.content.documentType.key) {
|
||||
loadContent();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace Umbraco.Web.Editors
|
||||
throw new ArgumentOutOfRangeException("The entity type was not a content type");
|
||||
}
|
||||
|
||||
var contentTypesWhereCompositionIsUsed = Services.ContentTypeService.GetWhereCompositionIsUsedInContentTypes(source, allContentTypes);
|
||||
var contentTypesWhereCompositionIsUsed = source.GetWhereCompositionIsUsedInContentTypes(allContentTypes);
|
||||
return contentTypesWhereCompositionIsUsed
|
||||
.Select(x => Mapper.Map<IContentTypeComposition, EntityBasic>(x))
|
||||
.Select(x =>
|
||||
|
||||
@@ -198,6 +198,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
private void InitializeRepositoryEvents()
|
||||
{
|
||||
//fixme: The reason these events are in the repository is for legacy, the events should exist at the service
|
||||
// level now since we can fire these events within the transaction... so move the events to service level
|
||||
|
||||
// plug repository event handlers
|
||||
// these trigger within the transaction to ensure consistency
|
||||
// and are used to maintain the central, database-level XML cache
|
||||
@@ -212,9 +215,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity;
|
||||
|
||||
// plug
|
||||
ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity;
|
||||
ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity;
|
||||
}
|
||||
|
||||
private void TearDownRepositoryEvents()
|
||||
@@ -229,9 +232,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
//MemberRepository.RemovedVersion -= OnMemberRemovedVersion;
|
||||
MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity;
|
||||
|
||||
ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
||||
ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
|
||||
@@ -194,9 +194,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity;
|
||||
|
||||
// plug
|
||||
ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity;
|
||||
ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity;
|
||||
|
||||
_withRepositoryEvents = true;
|
||||
}
|
||||
@@ -213,9 +213,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
MemberRepository.ScopeVersionRemove -= OnMemberRemovingVersion;
|
||||
MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity;
|
||||
|
||||
ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
||||
ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity;
|
||||
MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity;
|
||||
MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity;
|
||||
|
||||
_withRepositoryEvents = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user