Merge remote-tracking branch 'origin/temp8' into temp8-231-send-to-publish-with-variants
# Conflicts: # src/Umbraco.Core/Services/Implement/ContentService.cs
This commit is contained in:
@@ -115,7 +115,7 @@ namespace Umbraco.Core
|
||||
/// <summary>
|
||||
/// Determines whether a variation varies by culture and segment.
|
||||
/// </summary>
|
||||
public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) > 0;
|
||||
public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) == ContentVariation.CultureAndSegment;
|
||||
|
||||
/// <summary>
|
||||
/// Validates that a combination of culture and segment is valid for the variation.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -35,6 +36,7 @@ namespace Umbraco.Core.Models
|
||||
IconClosed = iconClosed;
|
||||
IconOpened = iconOpened;
|
||||
Type = type;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -85,6 +87,33 @@ namespace Umbraco.Core.Models
|
||||
/// <value>The type.</value>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the localized root node display name
|
||||
/// </summary>
|
||||
/// <param name="textService"></param>
|
||||
/// <returns></returns>
|
||||
public string GetRootNodeDisplayName(ILocalizedTextService textService)
|
||||
{
|
||||
var label = $"[{Alias}]";
|
||||
|
||||
// try to look up a the localized tree header matching the tree alias
|
||||
var localizedLabel = textService.Localize("treeHeaders/" + Alias);
|
||||
|
||||
// if the localizedLabel returns [alias] then return the title attribute from the trees.config file, if it's defined
|
||||
if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Title) == false)
|
||||
label = Title;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the localizedLabel translated into something that's not just [alias], so use the translation
|
||||
label = localizedLabel;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private Type _runtimeType;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace Umbraco.Core.Models
|
||||
/// Initializes a new instance of the <see cref="ContentCultureInfos"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>Used for cloning, without change tracking.</remarks>
|
||||
private ContentCultureInfos(string culture, string name, DateTime date)
|
||||
: this(culture)
|
||||
internal ContentCultureInfos(ContentCultureInfos other)
|
||||
: this(other.Culture)
|
||||
{
|
||||
_name = name;
|
||||
_date = date;
|
||||
_name = other.Name;
|
||||
_date = other.Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,7 +61,7 @@ namespace Umbraco.Core.Models
|
||||
/// <inheritdoc />
|
||||
public object DeepClone()
|
||||
{
|
||||
return new ContentCultureInfos(Culture, Name, Date);
|
||||
return new ContentCultureInfos(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -24,8 +24,12 @@ namespace Umbraco.Core.Models
|
||||
public ContentCultureInfosCollection(IEnumerable<ContentCultureInfos> items)
|
||||
: base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase)
|
||||
{
|
||||
// make sure to add *copies* and not the original items,
|
||||
// as items can be modified by AddOrUpdate, and therefore
|
||||
// the new collection would be impacted by changes made
|
||||
// to the old collection
|
||||
foreach (var item in items)
|
||||
Add(item);
|
||||
Add(new ContentCultureInfos(item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -63,20 +63,5 @@ namespace Umbraco.Core.Models
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,27 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property types obtained via composition.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Gets them raw, ie with their original variation.</para>
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
internal IEnumerable<PropertyType> RawComposedPropertyTypes => GetRawComposedPropertyTypes();
|
||||
|
||||
private IEnumerable<PropertyType> GetRawComposedPropertyTypes(bool start = true)
|
||||
{
|
||||
var propertyTypes = ContentTypeComposition
|
||||
.Cast<ContentTypeCompositionBase>()
|
||||
.SelectMany(x => start ? x.GetRawComposedPropertyTypes(false) : x.CompositionPropertyTypes);
|
||||
|
||||
if (!start)
|
||||
propertyTypes = propertyTypes.Union(PropertyTypes);
|
||||
|
||||
return propertyTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a content type to the composition.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,13 +11,6 @@ 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>
|
||||
/// Derives a unique alias from an existing alias.
|
||||
/// </summary>
|
||||
|
||||
@@ -67,7 +67,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, IsPublishing, this, _templateRepository);
|
||||
}
|
||||
|
||||
|
||||
protected override IEnumerable<IContentType> PerformGetAll(params Guid[] ids)
|
||||
{
|
||||
// use the underlying GetAll which will force cache all content types
|
||||
|
||||
+272
-152
@@ -119,7 +119,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected void PersistNewBaseContentType(IContentTypeComposition entity)
|
||||
{
|
||||
|
||||
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
|
||||
|
||||
//Cannot add a duplicate content type type
|
||||
@@ -234,7 +233,6 @@ AND umbracoNode.nodeObjectType = @objectType",
|
||||
|
||||
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
|
||||
{
|
||||
|
||||
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
|
||||
|
||||
// ensure the alias is not used already
|
||||
@@ -270,8 +268,8 @@ AND umbracoNode.id <> @id",
|
||||
// 1. Find content based on the current ContentType: entity.Id
|
||||
// 2. Find all PropertyTypes on the ContentType that was removed - tracked id (key)
|
||||
// 3. Remove properties based on property types from the removed content type where the content ids correspond to those found in step one
|
||||
var compositionBase = entity as ContentTypeCompositionBase;
|
||||
if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null &&
|
||||
if (entity is ContentTypeCompositionBase compositionBase &&
|
||||
compositionBase.RemovedContentTypeKeyTracker != null &&
|
||||
compositionBase.RemovedContentTypeKeyTracker.Any())
|
||||
{
|
||||
//TODO: Could we do the below with bulk SQL statements instead of looking everything up and then manipulating?
|
||||
@@ -314,7 +312,7 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
}
|
||||
|
||||
// delete the allowed content type entries before re-inserting the collectino of allowed content types
|
||||
// delete the allowed content type entries before re-inserting the collection of allowed content types
|
||||
Database.Delete<ContentTypeAllowedContentTypeDto>("WHERE Id = @Id", new { entity.Id });
|
||||
foreach (var allowedContentType in entity.AllowedContentTypes)
|
||||
{
|
||||
@@ -409,40 +407,34 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
|
||||
//check if the content type variation has been changed
|
||||
var ctVariationChanging = entity.IsPropertyDirty("Variations");
|
||||
if (ctVariationChanging)
|
||||
var contentTypeVariationDirty = entity.IsPropertyDirty("Variations");
|
||||
var oldContentTypeVariation = (ContentVariation) dtoPk.Variations;
|
||||
var newContentTypeVariation = entity.Variations;
|
||||
var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation;
|
||||
if (contentTypeVariationChanging)
|
||||
{
|
||||
//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);
|
||||
MoveContentTypeVariantData(entity, oldContentTypeVariation, newContentTypeVariation);
|
||||
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>();
|
||||
// collect property types that have a dirty variation
|
||||
List<PropertyType> propertyTypeVariationDirty = null;
|
||||
|
||||
// insert or update properties
|
||||
// all of them, no-group and in-groups
|
||||
// note: this only deals with *local* property types, we're dealing w/compositions later below
|
||||
foreach (var propertyType in entity.PropertyTypes)
|
||||
{
|
||||
//if the content type variation isn't changing track if any property type is changing
|
||||
if (!ctVariationChanging)
|
||||
if (contentTypeVariationChanging)
|
||||
{
|
||||
if (propertyType.IsPropertyDirty("Variations"))
|
||||
// content type is changing
|
||||
switch (newContentTypeVariation)
|
||||
{
|
||||
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
|
||||
case ContentVariation.Nothing: // changing to Nothing
|
||||
// all property types must change to Nothing
|
||||
propertyType.Variations = ContentVariation.Nothing;
|
||||
break;
|
||||
case ContentVariation.Culture:
|
||||
//we don't need to modify the property type in this case
|
||||
case ContentVariation.Culture: // changing to Culture
|
||||
// all property types can remain Nothing
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
@@ -451,15 +443,65 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
}
|
||||
|
||||
var groupId = propertyType.PropertyGroupId?.Value ?? default(int);
|
||||
// then, track each property individually
|
||||
if (propertyType.IsPropertyDirty("Variations"))
|
||||
{
|
||||
// allocate the list only when needed
|
||||
if (propertyTypeVariationDirty == null)
|
||||
propertyTypeVariationDirty = new List<PropertyType>();
|
||||
|
||||
propertyTypeVariationDirty.Add(propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
// figure out dirty property types that have actually changed
|
||||
// before we insert or update properties, so we can read the old variations
|
||||
var propertyTypeVariationChanges = propertyTypeVariationDirty != null
|
||||
? GetPropertyVariationChanges(propertyTypeVariationDirty)
|
||||
: null;
|
||||
|
||||
// deal with composition property types
|
||||
// add changes for property types obtained via composition, which change due
|
||||
// to this content type variations change
|
||||
if (contentTypeVariationChanging)
|
||||
{
|
||||
// must use RawComposedPropertyTypes here: only those types that are obtained
|
||||
// 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)
|
||||
{
|
||||
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
|
||||
throw new NotSupportedException(); // TODO: support this
|
||||
|
||||
if (propertyType.Variations == ContentVariation.Culture)
|
||||
{
|
||||
if (propertyTypeVariationChanges == null)
|
||||
propertyTypeVariationChanges = new Dictionary<int, (ContentVariation, ContentVariation)>();
|
||||
|
||||
// if content type moves to Culture, property type becomes Culture here again
|
||||
// if content type moves to Nothing, property type becomes Nothing here
|
||||
if (newContentTypeVariation == ContentVariation.Culture)
|
||||
propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Nothing, ContentVariation.Culture);
|
||||
else if (newContentTypeVariation == ContentVariation.Nothing)
|
||||
propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Culture, ContentVariation.Nothing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert or update properties
|
||||
// all of them, no-group and in-groups
|
||||
foreach (var propertyType in entity.PropertyTypes)
|
||||
{
|
||||
// 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))
|
||||
if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default)
|
||||
AssignDataTypeFromPropertyEditor(propertyType);
|
||||
|
||||
// validate the alias
|
||||
ValidateAlias(propertyType);
|
||||
|
||||
// insert or update property
|
||||
var groupId = propertyType.PropertyGroupId?.Value ?? default;
|
||||
var propertyTypeDto = PropertyGroupFactory.BuildPropertyTypeDto(groupId, propertyType, entity.Id);
|
||||
var typeId = propertyType.HasIdentity
|
||||
? Database.Update(propertyTypeDto)
|
||||
@@ -470,31 +512,22 @@ AND umbracoNode.id <> @id",
|
||||
typeId = propertyType.Id;
|
||||
|
||||
// not an orphan anymore
|
||||
if (orphanPropertyTypeIds != null)
|
||||
orphanPropertyTypeIds.Remove(typeId);
|
||||
orphanPropertyTypeIds?.Remove(typeId);
|
||||
}
|
||||
|
||||
//check if any property types were changing variation
|
||||
if (propertyTypeVariationChanges.Count > 0)
|
||||
{
|
||||
var changes = new Dictionary<int, (ContentVariation, ContentVariation)>();
|
||||
// must restrict property data changes to impacted content types - if changing a composing
|
||||
// type, some composed types (those that do not vary) are not impacted and should be left
|
||||
// unchanged
|
||||
//
|
||||
// getting 'all' from the cache policy is prone to race conditions - fast but dangerous
|
||||
//var all = ((FullDataSetRepositoryCachePolicy<TEntity, int>)CachePolicy).GetAllCached(PerformGetAll);
|
||||
var all = PerformGetAll();
|
||||
|
||||
//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);
|
||||
}
|
||||
var impacted = GetImpactedContentTypes(entity, all);
|
||||
|
||||
// if some property types have actually changed, move their variant data
|
||||
if (propertyTypeVariationChanges != null)
|
||||
MovePropertyTypeVariantData(propertyTypeVariationChanges, impacted);
|
||||
|
||||
// deal with orphan properties: those that were in a deleted tab,
|
||||
// and have not been re-mapped to another tab or to 'generic properties'
|
||||
@@ -503,6 +536,77 @@ AND umbracoNode.id <> @id",
|
||||
DeletePropertyType(entity.Id, id);
|
||||
}
|
||||
|
||||
private IEnumerable<IContentTypeComposition> GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable<IContentTypeComposition> all)
|
||||
{
|
||||
var impact = new List<IContentTypeComposition>();
|
||||
var set = new List<IContentTypeComposition> { contentType };
|
||||
|
||||
var tree = new Dictionary<int, List<IContentTypeComposition>>();
|
||||
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<IContentTypeComposition>();
|
||||
list.Add(x);
|
||||
}
|
||||
|
||||
var nset = new List<IContentTypeComposition>();
|
||||
do
|
||||
{
|
||||
impact.AddRange(set);
|
||||
|
||||
foreach (var x in set)
|
||||
{
|
||||
if (!tree.TryGetValue(x.Id, out var list)) continue;
|
||||
nset.AddRange(list.Where(y => y.VariesByCulture()));
|
||||
}
|
||||
|
||||
set = nset;
|
||||
nset = new List<IContentTypeComposition>();
|
||||
} while (set.Count > 0);
|
||||
|
||||
return impact;
|
||||
}
|
||||
|
||||
// gets property types that have actually changed, and the corresponding changes
|
||||
// returns null if no property type has actually changed
|
||||
private Dictionary<int, (ContentVariation FromVariation, ContentVariation ToVariation)> GetPropertyVariationChanges(IEnumerable<PropertyType> propertyTypes)
|
||||
{
|
||||
var propertyTypesL = propertyTypes.ToList();
|
||||
|
||||
// select the current variations (before the change) from database
|
||||
var selectCurrentVariations = Sql()
|
||||
.Select<PropertyTypeDto>(x => x.Id, x => x.Variations)
|
||||
.From<PropertyTypeDto>()
|
||||
.WhereIn<PropertyTypeDto>(x => x.Id, propertyTypesL.Select(x => x.Id));
|
||||
|
||||
var oldVariations = Database.Dictionary<int, byte>(selectCurrentVariations);
|
||||
|
||||
// build a dictionary of actual changes
|
||||
Dictionary<int, (ContentVariation, ContentVariation)> changes = null;
|
||||
|
||||
foreach (var propertyType in propertyTypesL)
|
||||
{
|
||||
// new property type, ignore
|
||||
if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB))
|
||||
continue;
|
||||
var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly
|
||||
|
||||
// only those property types that *actually* changed
|
||||
var newVariation = propertyType.Variations;
|
||||
if (oldVariation == newVariation)
|
||||
continue;
|
||||
|
||||
// allocate the dictionary only when needed
|
||||
if (changes == null)
|
||||
changes = new Dictionary<int, (ContentVariation, ContentVariation)>();
|
||||
|
||||
changes[propertyType.Id] = (oldVariation, newVariation);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear any redirects associated with content for a content type
|
||||
/// </summary>
|
||||
@@ -529,28 +633,39 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves variant data for property type changes
|
||||
/// Gets the default language identifier.
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeChanges"></param>
|
||||
private void MoveVariantData(IDictionary<int, (ContentVariation, ContentVariation)> propertyTypeChanges)
|
||||
private int GetDefaultLanguageId()
|
||||
{
|
||||
var defaultLangId = Database.First<int>(Sql().Select<LanguageDto>(x => x.Id).From<LanguageDto>().Where<LanguageDto>(x => x.IsDefault));
|
||||
var selectDefaultLanguageId = Sql()
|
||||
.Select<LanguageDto>(x => x.Id)
|
||||
.From<LanguageDto>()
|
||||
.Where<LanguageDto>(x => x.IsDefault);
|
||||
|
||||
return Database.First<int>(selectDefaultLanguageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves variant data for property type variation changes.
|
||||
/// </summary>
|
||||
private void MovePropertyTypeVariantData(IDictionary<int, (ContentVariation FromVariation, ContentVariation ToVariation)> propertyTypeChanges, IEnumerable<IContentTypeComposition> impacted)
|
||||
{
|
||||
var defaultLanguageId = GetDefaultLanguageId();
|
||||
var impactedL = impacted.Select(x => x.Id).ToList();
|
||||
|
||||
//Group by the "To" variation so we can bulk update in the correct batches
|
||||
foreach(var g in propertyTypeChanges.GroupBy(x => x.Value.Item2))
|
||||
foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
|
||||
{
|
||||
var propertyTypeIds = g.Select(s => s.Key).ToList();
|
||||
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
|
||||
var toVariation = grouping.Key;
|
||||
|
||||
//the ContentVariation that the data is moving "To"
|
||||
var toVariantType = g.Key;
|
||||
|
||||
switch(toVariantType)
|
||||
switch (toVariation)
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
MovePropertyDataToVariantCulture(defaultLangId, propertyTypeIds: propertyTypeIds);
|
||||
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
|
||||
break;
|
||||
case ContentVariation.Nothing:
|
||||
MovePropertyDataToVariantNothing(defaultLangId, propertyTypeIds: propertyTypeIds);
|
||||
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
|
||||
break;
|
||||
case ContentVariation.CultureAndSegment:
|
||||
case ContentVariation.Segment:
|
||||
@@ -561,24 +676,17 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves variant data for a content type variation change
|
||||
/// 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)
|
||||
private void MoveContentTypeVariantData(IContentTypeComposition contentType, ContentVariation fromVariation, ContentVariation toVariation)
|
||||
{
|
||||
var defaultLangId = Database.First<int>(Sql().Select<LanguageDto>(x => x.Id).From<LanguageDto>().Where<LanguageDto>(x => x.IsDefault));
|
||||
var defaultLanguageId = GetDefaultLanguageId();
|
||||
|
||||
var sqlPropertyTypeIds = Sql().Select<PropertyTypeDto>(x => x.Id).From<PropertyTypeDto>().Where<PropertyTypeDto>(x => x.ContentTypeId == contentType.Id);
|
||||
switch (to)
|
||||
switch (toVariation)
|
||||
{
|
||||
case ContentVariation.Culture:
|
||||
//move the property data
|
||||
|
||||
MovePropertyDataToVariantCulture(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds);
|
||||
|
||||
//now we need to move the names
|
||||
//move the names
|
||||
//first clear out any existing names that might already exists under the default lang
|
||||
//there's 2x tables to update
|
||||
|
||||
@@ -588,10 +696,11 @@ AND umbracoNode.id <> @id",
|
||||
.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);
|
||||
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
|
||||
var sqlDelete = Sql()
|
||||
.Delete<ContentVersionCultureVariationDto>()
|
||||
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//clear out the documentCultureVariation table
|
||||
@@ -599,10 +708,11 @@ AND umbracoNode.id <> @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);
|
||||
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
|
||||
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
|
||||
@@ -610,32 +720,31 @@ AND umbracoNode.id <> @id",
|
||||
//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
|
||||
.Append($", {defaultLanguageId}") //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
|
||||
.Append($", 1, {defaultLanguageId}") //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.
|
||||
//we don't 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
|
||||
@@ -649,73 +758,102 @@ AND umbracoNode.id <> @id",
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will move all property data from variant to invariant
|
||||
/// Copies property data from one language to another.
|
||||
/// </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)
|
||||
/// <param name="sourceLanguageId">The source language (can be null ie invariant).</param>
|
||||
/// <param name="targetLanguageId">The target language (can be null ie invariant)</param>
|
||||
/// <param name="propertyTypeIds">The property type identifiers.</param>
|
||||
/// <param name="contentTypeIds">The content type identifiers.</param>
|
||||
private void CopyPropertyData(int? sourceLanguageId, int? targetLanguageId, IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> contentTypeIds = null)
|
||||
{
|
||||
//first clear out any existing property data that might already exists under the default lang
|
||||
// fixme - should we batch then?
|
||||
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
|
||||
if (whereInArgsCount > 2000)
|
||||
throw new NotSupportedException("Too many property/content types.");
|
||||
|
||||
//first clear out any existing property data that might already exists under the target language
|
||||
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);
|
||||
.Delete<PropertyDataDto>();
|
||||
|
||||
// not ok for SqlCe (no JOIN in DELETE)
|
||||
//if (contentTypeIds != null)
|
||||
// sqlDelete
|
||||
// .From<PropertyDataDto>()
|
||||
// .InnerJoin<ContentVersionDto>().On<PropertyDataDto, ContentVersionDto>((pdata, cversion) => pdata.VersionId == cversion.Id)
|
||||
// .InnerJoin<ContentDto>().On<ContentVersionDto, ContentDto>((cversion, c) => cversion.NodeId == c.NodeId);
|
||||
|
||||
Sql<ISqlContext> inSql = null;
|
||||
if (contentTypeIds != null)
|
||||
{
|
||||
inSql = Sql()
|
||||
.Select<ContentVersionDto>(x => x.Id)
|
||||
.From<ContentVersionDto>()
|
||||
.InnerJoin<ContentDto>().On<ContentVersionDto, ContentDto>((cversion, c) => cversion.NodeId == c.NodeId)
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.VersionId, inSql);
|
||||
}
|
||||
|
||||
// NPoco cannot turn the clause into IS NULL with a nullable parameter - deal with it
|
||||
if (targetLanguageId == null)
|
||||
sqlDelete.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
else
|
||||
sqlDelete.Where<PropertyDataDto>(x => x.LanguageId == targetLanguageId);
|
||||
|
||||
sqlDelete
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
// see note above, not ok for SqlCe
|
||||
//if (contentTypeIds != null)
|
||||
// sqlDelete
|
||||
// .WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
|
||||
//now insert all property data into the default language that exists under the invariant lang
|
||||
//now insert all property data into the target language that exists under the source language
|
||||
var targetLanguageIdS = targetLanguageId.HasValue ? targetLanguageId.ToString() : "NULL";
|
||||
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);
|
||||
.Append(", " + targetLanguageIdS) //default language ID
|
||||
.From<PropertyDataDto>();
|
||||
|
||||
if (contentTypeIds != null)
|
||||
sqlSelectData
|
||||
.InnerJoin<ContentVersionDto>().On<PropertyDataDto, ContentVersionDto>((pdata, cversion) => pdata.VersionId == cversion.Id)
|
||||
.InnerJoin<ContentDto>().On<ContentVersionDto, ContentDto>((cversion, c) => cversion.NodeId == c.NodeId);
|
||||
|
||||
// NPoco cannot turn the clause into IS NULL with a nullable parameter - deal with it
|
||||
if (sourceLanguageId == null)
|
||||
sqlSelectData.Where<PropertyDataDto>(x => x.LanguageId == null);
|
||||
else
|
||||
sqlSelectData.Where<PropertyDataDto>(x => x.LanguageId == sourceLanguageId);
|
||||
|
||||
sqlSelectData
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
if (contentTypeIds != null)
|
||||
sqlSelectData
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
|
||||
|
||||
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);
|
||||
// when copying from Culture, keep the original values around in case we want to go back
|
||||
// when copying from Nothing, kill the original values, we don't want them around
|
||||
if (sourceLanguageId == null)
|
||||
{
|
||||
sqlDelete = Sql()
|
||||
.Delete<PropertyDataDto>();
|
||||
|
||||
Database.Execute(sqlDelete);
|
||||
if (contentTypeIds != null)
|
||||
sqlDelete.WhereIn<PropertyDataDto>(x => x.VersionId, inSql);
|
||||
|
||||
//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);
|
||||
sqlDelete
|
||||
.Where<PropertyDataDto>(x => x.LanguageId == null)
|
||||
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
|
||||
|
||||
Database.Execute(sqlInsert);
|
||||
Database.Execute(sqlDelete);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeletePropertyType(int contentTypeId, int propertyTypeId)
|
||||
@@ -851,24 +989,6 @@ 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>()
|
||||
.InnerJoin<ContentType2ContentTypeDto>()
|
||||
.On<NodeDto, ContentType2ContentTypeDto>(left => left.NodeId, right => right.ChildId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.Where<ContentType2ContentTypeDto>(x => x.ParentId == id);
|
||||
var dtos = Database.Fetch<NodeDto>(sql);
|
||||
return dtos.Any()
|
||||
? GetMany(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray())
|
||||
: Enumerable.Empty<TEntity>();
|
||||
}
|
||||
|
||||
internal static class ContentTypeQueryMapper
|
||||
{
|
||||
public class AssociatedTemplate
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
|
||||
@@ -30,12 +30,12 @@ namespace Umbraco.Core.Services
|
||||
string[] filterPropertyTypes = null)
|
||||
{
|
||||
filterContentTypes = filterContentTypes == null
|
||||
? new string[] { }
|
||||
: filterContentTypes.Where(x => x.IsNullOrWhiteSpace() == false).ToArray();
|
||||
? Array.Empty<string>()
|
||||
: filterContentTypes.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
|
||||
|
||||
filterPropertyTypes = filterPropertyTypes == null
|
||||
? new string[] {}
|
||||
: filterPropertyTypes.Where(x => x.IsNullOrWhiteSpace() == false).ToArray();
|
||||
? Array.Empty<string>()
|
||||
: filterPropertyTypes.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
|
||||
|
||||
//create the full list of property types to use as the filter
|
||||
//this is the combination of all property type aliases found in the content types passed in for the filter
|
||||
@@ -47,7 +47,7 @@ namespace Umbraco.Core.Services
|
||||
.Union(filterPropertyTypes)
|
||||
.ToArray();
|
||||
|
||||
var sourceId = source != null ? source.Id : 0;
|
||||
var sourceId = source?.Id ?? 0;
|
||||
|
||||
// find out if any content type uses this content type
|
||||
var isUsing = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray();
|
||||
@@ -161,6 +161,5 @@ namespace Umbraco.Core.Services
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +368,11 @@ namespace Umbraco.Core.Services
|
||||
/// <para>A publishing document is a document with values that are being published, i.e.
|
||||
/// that have been published or cleared via <see cref="IContent.PublishCulture"/> and
|
||||
/// <see cref="IContent.UnpublishCulture"/>.</para>
|
||||
/// <para>When one needs to publish or unpublish a single culture, or all cultures, using <see cref="SaveAndPublish"/>
|
||||
/// and <see cref="Unpublish"/> is the way to go. But if one needs to, say, publish two cultures and unpublish a third
|
||||
/// one, in one go, then one needs to invoke <see cref="IContent.PublishCulture"/> and <see cref="IContent.UnpublishCulture"/>
|
||||
/// on the content itself - this prepares the content, but does not commit anything - and then, invoke
|
||||
/// <see cref="SavePublishing"/> to actually commit the changes to the database.</para>
|
||||
/// <para>The document is *always* saved, even when publishing fails.</para>
|
||||
/// </remarks>
|
||||
PublishResult SavePublishing(IContent content, int userId = 0, bool raiseEvents = true);
|
||||
@@ -375,11 +380,30 @@ namespace Umbraco.Core.Services
|
||||
/// <summary>
|
||||
/// Saves and publishes a document branch.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Unless specified, all cultures are re-published. Otherwise, one culture can be specified. To act on more
|
||||
/// that one culture, see the other overload of this method.</para>
|
||||
/// <para>The <paramref name="force"/> parameter determines which documents are published. When <c>false</c>,
|
||||
/// only those documents that are already published, are republished. When <c>true</c>, all documents are
|
||||
/// published.</para>
|
||||
/// </remarks>
|
||||
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Saves and publishes a document branch.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The <paramref name="force"/> parameter determines which documents are published. When <c>false</c>,
|
||||
/// only those documents that are already published, are republished. When <c>true</c>, all documents are
|
||||
/// published.</para>
|
||||
/// <para>The <paramref name="editing"/> parameter is a function which determines whether a document has
|
||||
/// values to publish (else there is no need to publish it). If one wants to publish only a selection of
|
||||
/// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
|
||||
/// cultures may trigger an unwanted republish.</para>
|
||||
/// <para>The <paramref name="publishCultures"/> parameter is a function to execute to publish cultures, on
|
||||
/// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
|
||||
/// whether the cultures could be published.</para>
|
||||
/// </remarks>
|
||||
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, Func<IContent, bool> editing, Func<IContent, bool> publishCultures, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1272,12 +1272,49 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
bool IsEditing(IContent c, string l)
|
||||
=> c.PublishName != c.Name ||
|
||||
c.PublishedCultures.Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
|
||||
c.Properties.Any(x => x.Values.Where(y => culture == "*" || y.Culture == l).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
|
||||
c.PublishedCultures.Where(x => x.InvariantEquals(l)).Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
|
||||
c.Properties.Any(x => x.Values.Where(y => culture == "*" || y.Culture.InvariantEquals(l)).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
|
||||
|
||||
return SaveAndPublishBranch(content, force, document => IsEditing(document, culture), document => document.PublishCulture(culture), userId);
|
||||
}
|
||||
|
||||
// fixme - make this public once we know it works + document
|
||||
private IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = 0)
|
||||
{
|
||||
// note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
|
||||
// and not to == them, else we would be comparing references, and that is a bad thing
|
||||
|
||||
cultures = cultures ?? Array.Empty<string>();
|
||||
|
||||
// determines whether the document is edited, and thus needs to be published,
|
||||
// for the specified cultures (it may be edited for other cultures and that
|
||||
// should not trigger a publish).
|
||||
bool IsEdited(IContent c)
|
||||
{
|
||||
if (cultures.Length == 0)
|
||||
{
|
||||
// nothing = everything
|
||||
return c.PublishName != c.Name ||
|
||||
c.PublishedCultures.Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
|
||||
c.Properties.Any(x => x.Values.Any(y => !y.EditedValue.Equals(y.PublishedValue)));
|
||||
}
|
||||
|
||||
return c.PublishName != c.Name ||
|
||||
c.PublishedCultures.Where(x => cultures.Contains(x, StringComparer.InvariantCultureIgnoreCase)).Any(x => c.GetCultureName(x) != c.GetPublishName(x)) ||
|
||||
c.Properties.Any(x => x.Values.Where(y => cultures.Contains(y.Culture, StringComparer.InvariantCultureIgnoreCase)).Any(y => !y.EditedValue.Equals(y.PublishedValue)));
|
||||
}
|
||||
|
||||
// publish the specified cultures
|
||||
bool PublishCultures(IContent c)
|
||||
{
|
||||
return cultures.Length == 0
|
||||
? c.PublishCulture() // nothing = everything
|
||||
: cultures.All(c.PublishCulture);
|
||||
}
|
||||
|
||||
return SaveAndPublishBranch(content, force, IsEdited, PublishCultures, userId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
|
||||
Func<IContent, bool> editing, Func<IContent, bool> publishCultures, int userId = 0)
|
||||
|
||||
+10
-29
@@ -344,37 +344,18 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetComposedOf(int id, IEnumerable<TItem> all)
|
||||
{
|
||||
return all.Where(x => x.ContentTypeComposition.Any(y => y.Id == id));
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// hash set handles duplicates
|
||||
var composed = new HashSet<TItem>(new DelegateEqualityComparer<TItem>(
|
||||
(x, y) => x.Id == y.Id,
|
||||
x => x.Id.GetHashCode()));
|
||||
|
||||
var ids = new Stack<int>();
|
||||
ids.Push(id);
|
||||
|
||||
while (ids.Count > 0)
|
||||
{
|
||||
var i = ids.Pop();
|
||||
var result = Repository.GetTypesDirectlyComposedOf(i).ToArray();
|
||||
|
||||
foreach (var c in result)
|
||||
{
|
||||
composed.Add(c);
|
||||
ids.Push(c.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return composed.ToArray();
|
||||
}
|
||||
// GetAll is cheap, repository has a full dataset cache policy
|
||||
// fixme - still, because it uses the cache, race conditions!
|
||||
var allContentTypes = GetAll(Array.Empty<int>());
|
||||
return GetComposedOf(id, allContentTypes);
|
||||
}
|
||||
|
||||
public int Count()
|
||||
|
||||
@@ -49,6 +49,8 @@ namespace Umbraco.Tests.Composing
|
||||
public bool ShowInNotifier => false;
|
||||
|
||||
public bool CanBePermissionAssigned => true;
|
||||
|
||||
public bool OpensDialog => true;
|
||||
}
|
||||
|
||||
public class NonSingletonAction : IAction
|
||||
@@ -66,6 +68,8 @@ namespace Umbraco.Tests.Composing
|
||||
public bool ShowInNotifier => false;
|
||||
|
||||
public bool CanBePermissionAssigned => true;
|
||||
|
||||
public bool OpensDialog => true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Runtime.Remoting;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NPoco;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Exceptions;
|
||||
@@ -12,10 +10,9 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Tests.Scoping;
|
||||
|
||||
namespace Umbraco.Tests.Services
|
||||
{
|
||||
|
||||
@@ -0,0 +1,773 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using LightInject;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.PublishedCache.NuCache;
|
||||
using Umbraco.Web.PublishedCache.NuCache.DataSource;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Tests.Services
|
||||
{
|
||||
[TestFixture]
|
||||
[Apartment(ApartmentState.STA)]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true, WithApplication = true)]
|
||||
public class ContentTypeServiceVariantsTests : TestWithSomeContentBase
|
||||
{
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
// pfew - see note in ScopedNuCacheTests?
|
||||
Container.RegisterSingleton<IServerMessenger, LocalServerMessenger>();
|
||||
Container.RegisterSingleton(f => Mock.Of<IServerRegistrar>());
|
||||
Container.RegisterCollectionBuilder<CacheRefresherCollectionBuilder>()
|
||||
.Add(f => f.TryGetInstance<TypeLoader>().GetCacheRefreshers());
|
||||
}
|
||||
|
||||
protected override IPublishedSnapshotService CreatePublishedSnapshotService()
|
||||
{
|
||||
var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true };
|
||||
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
|
||||
var runtimeStateMock = new Mock<IRuntimeState>();
|
||||
runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run);
|
||||
|
||||
var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), Mock.Of<IDataTypeService>());
|
||||
//var documentRepository = Mock.Of<IDocumentRepository>();
|
||||
var documentRepository = Container.GetInstance<IDocumentRepository>();
|
||||
var mediaRepository = Mock.Of<IMediaRepository>();
|
||||
var memberRepository = Mock.Of<IMemberRepository>();
|
||||
|
||||
return new PublishedSnapshotService(
|
||||
options,
|
||||
null,
|
||||
runtimeStateMock.Object,
|
||||
ServiceContext,
|
||||
contentTypeFactory,
|
||||
null,
|
||||
publishedSnapshotAccessor,
|
||||
Mock.Of<IVariationContextAccessor>(),
|
||||
Logger,
|
||||
ScopeProvider,
|
||||
documentRepository, mediaRepository, memberRepository,
|
||||
DefaultCultureAccessor,
|
||||
new DatabaseDataSource(),
|
||||
Container.GetInstance<IGlobalSettings>(), new SiteDomainHelper());
|
||||
}
|
||||
|
||||
public class LocalServerMessenger : ServerMessengerBase
|
||||
{
|
||||
public LocalServerMessenger()
|
||||
: base(false)
|
||||
{ }
|
||||
|
||||
protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable<object> ids = null, string json = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertJsonStartsWith(int id, string expected)
|
||||
{
|
||||
var json = GetJson(id).Replace('"', '\'');
|
||||
var pos = json.IndexOf("'cultureData':", StringComparison.InvariantCultureIgnoreCase);
|
||||
json = json.Substring(0, pos + "'cultureData':".Length);
|
||||
Assert.AreEqual(expected, json);
|
||||
}
|
||||
|
||||
private string GetJson(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var selectJson = SqlContext.Sql().Select<ContentNuDto>().From<ContentNuDto>().Where<ContentNuDto>(x => x.NodeId == id && !x.Published);
|
||||
var dto = scope.Database.Fetch<ContentNuDto>(selectJson).FirstOrDefault();
|
||||
Assert.IsNotNull(dto);
|
||||
var json = dto.Data;
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = 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
|
||||
}
|
||||
};
|
||||
|
||||
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", "en");
|
||||
document.SetValue("value1", "v1fr", "fr");
|
||||
document.SetValue("value2", "v2");
|
||||
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("v1fr", document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// switch content type to Nothing
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1en", document.Name);
|
||||
Assert.IsNull(document.GetCultureName("en"));
|
||||
Assert.IsNull(document.GetCultureName("fr"));
|
||||
Assert.IsNull(document.GetValue("value1", "en"));
|
||||
Assert.IsNull(document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v1en", document.GetValue("value1"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// switch content back to Culture
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
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"));
|
||||
Assert.IsNull(document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v1en", document.GetValue("value1"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// 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("doc1en", document.Name);
|
||||
Assert.AreEqual("doc1en", document.GetCultureName("en"));
|
||||
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
|
||||
Assert.AreEqual("v1en", document.GetValue("value1", "en"));
|
||||
Assert.AreEqual("v1fr", document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Assert.IsTrue(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Variations_SimpleContentType_InvariantToVariantAndBack()
|
||||
{
|
||||
// one simple content type, invariant
|
||||
// can change it to variant and back
|
||||
// can then switch one property to variant
|
||||
|
||||
var languageEn = new Language("en") { IsDefault = true };
|
||||
ServiceContext.LocalizationService.Save(languageEn);
|
||||
var languageFr = new Language("fr");
|
||||
ServiceContext.LocalizationService.Save(languageFr);
|
||||
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = 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
|
||||
}
|
||||
};
|
||||
|
||||
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
var document = (IContent) new Content("document", -1, contentType);
|
||||
document.Name = "doc1";
|
||||
document.SetValue("value1", "v1");
|
||||
document.SetValue("value2", "v2");
|
||||
ServiceContext.ContentService.Save(document);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1", document.Name);
|
||||
Assert.IsNull(document.GetCultureName("en"));
|
||||
Assert.IsNull(document.GetCultureName("fr"));
|
||||
Assert.IsNull(document.GetValue("value1", "en"));
|
||||
Assert.IsNull(document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v1", document.GetValue("value1"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// switch content type to Culture
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1", document.GetCultureName("en"));
|
||||
Assert.IsNull(document.GetCultureName("fr"));
|
||||
Assert.IsNull(document.GetValue("value1", "en"));
|
||||
Assert.IsNull(document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v1", document.GetValue("value1"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// switch property to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1", document.GetCultureName("en"));
|
||||
Assert.IsNull(document.GetCultureName("fr"));
|
||||
Assert.AreEqual("v1", document.GetValue("value1", "en"));
|
||||
Assert.IsNull(document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Assert.IsTrue(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// switch content back to Nothing
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
document = ServiceContext.ContentService.GetById(document.Id);
|
||||
Assert.AreEqual("doc1", document.Name);
|
||||
Assert.IsNull(document.GetCultureName("en"));
|
||||
Assert.IsNull(document.GetCultureName("fr"));
|
||||
Assert.IsNull(document.GetValue("value1", "en"));
|
||||
Assert.IsNull(document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v1", document.GetValue("value1"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Variations_SimpleContentType_VariantPropertyToInvariantAndBack()
|
||||
{
|
||||
// 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);
|
||||
|
||||
var contentType = new ContentType(-1)
|
||||
{
|
||||
Alias = "contentType",
|
||||
Name = "contentType",
|
||||
Variations = 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
|
||||
}
|
||||
};
|
||||
|
||||
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", "en");
|
||||
document.SetValue("value1", "v1fr", "fr");
|
||||
document.SetValue("value2", "v2");
|
||||
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("v1fr", document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// switch property type to Nothing
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
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"));
|
||||
Assert.IsNull(document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v1en", document.GetValue("value1"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Assert.IsFalse(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// 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("doc1en", document.Name);
|
||||
Assert.AreEqual("doc1en", document.GetCultureName("en"));
|
||||
Assert.AreEqual("doc1fr", document.GetCultureName("fr"));
|
||||
Assert.AreEqual("v1en", document.GetValue("value1", "en"));
|
||||
Assert.AreEqual("v1fr", document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2"));
|
||||
|
||||
Assert.IsTrue(document.ContentType.PropertyTypes.First(x => x.Alias == "value1").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
|
||||
// switch other property to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value2").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
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("v1fr", document.GetValue("value1", "fr"));
|
||||
Assert.AreEqual("v2", document.GetValue("value2", "en"));
|
||||
Assert.IsNull(document.GetValue("value2", "fr"));
|
||||
Assert.IsNull(document.GetValue("value2"));
|
||||
|
||||
Assert.IsTrue(document.ContentType.PropertyTypes.First(x => x.Alias == "value2").VariesByCulture());
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Variations_ComposedContentType_1()
|
||||
{
|
||||
// one composing content type, variant, with both variant and invariant properties
|
||||
// one composed content type, variant, with both variant and invariant properties
|
||||
// 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);
|
||||
|
||||
var composing = new ContentType(-1)
|
||||
{
|
||||
Alias = "composing",
|
||||
Name = "composing",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
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 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
|
||||
}
|
||||
};
|
||||
|
||||
composed.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" });
|
||||
composed.AddContentType(composing);
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
|
||||
var document = (IContent) new Content("document", -1, composed);
|
||||
document.SetCultureName("doc1en", "en");
|
||||
document.SetCultureName("doc1fr", "fr");
|
||||
document.SetValue("value11", "v11en", "en");
|
||||
document.SetValue("value11", "v11fr", "fr");
|
||||
document.SetValue("value12", "v12");
|
||||
document.SetValue("value21", "v21en", "en");
|
||||
document.SetValue("value21", "v21fr", "fr");
|
||||
document.SetValue("value22", "v22");
|
||||
ServiceContext.ContentService.Save(document);
|
||||
|
||||
// both value11 and value21 are variant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
composed.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
|
||||
// both value11 and value21 are invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
composed.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
|
||||
// value11 is variant again, but value21 is still invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
composed.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
composing.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
// value11 is invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
composing.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
// value11 is still invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Change_Variations_ComposedContentType_2()
|
||||
{
|
||||
// one composing content type, variant, with both variant and invariant properties
|
||||
// one composed content type, variant, with both variant and invariant properties
|
||||
// one composed content type, invariant
|
||||
// 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);
|
||||
|
||||
var composing = new ContentType(-1)
|
||||
{
|
||||
Alias = "composing",
|
||||
Name = "composing",
|
||||
Variations = ContentVariation.Culture
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
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 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
|
||||
}
|
||||
};
|
||||
|
||||
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 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
|
||||
}
|
||||
};
|
||||
|
||||
composed2.PropertyGroups.Add(new PropertyGroup(properties3) { Name = "Content" });
|
||||
composed2.AddContentType(composing);
|
||||
ServiceContext.ContentTypeService.Save(composed2);
|
||||
|
||||
var document1 = (IContent) new Content ("document1", -1, composed1);
|
||||
document1.SetCultureName("doc1en", "en");
|
||||
document1.SetCultureName("doc1fr", "fr");
|
||||
document1.SetValue("value11", "v11en", "en");
|
||||
document1.SetValue("value11", "v11fr", "fr");
|
||||
document1.SetValue("value12", "v12");
|
||||
document1.SetValue("value21", "v21en", "en");
|
||||
document1.SetValue("value21", "v21fr", "fr");
|
||||
document1.SetValue("value22", "v22");
|
||||
ServiceContext.ContentService.Save(document1);
|
||||
|
||||
var document2 = (IContent)new Content("document2", -1, composed2);
|
||||
document2.Name = "doc2";
|
||||
document2.SetValue("value11", "v11");
|
||||
document2.SetValue("value12", "v12");
|
||||
document2.SetValue("value31", "v31");
|
||||
document2.SetValue("value32", "v32");
|
||||
ServiceContext.ContentService.Save(document2);
|
||||
|
||||
// both value11 and value21 are variant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
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':");
|
||||
|
||||
composed1.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
|
||||
// both value11 and value21 are invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
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':");
|
||||
|
||||
composed1.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
|
||||
// value11 is variant again, but value21 is still invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
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':");
|
||||
|
||||
composed1.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
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':");
|
||||
|
||||
composing.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
// value11 is invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
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':");
|
||||
|
||||
composing.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
// value11 is still invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
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':");
|
||||
|
||||
composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
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':");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,7 @@ namespace Umbraco.Tests.TestHelpers
|
||||
IUmbracoSettingsSection umbracoSettings,
|
||||
IEventMessagesFactory eventMessagesFactory,
|
||||
IEnumerable<IUrlSegmentProvider> urlSegmentProviders,
|
||||
TypeLoader typeLoader,
|
||||
IServiceFactory container = null)
|
||||
{
|
||||
if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider));
|
||||
@@ -183,7 +184,7 @@ namespace Umbraco.Tests.TestHelpers
|
||||
var macroService = GetLazyService<IMacroService>(container, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo<IMacroRepository>(c), GetRepo<IAuditRepository>(c)));
|
||||
var packagingService = GetLazyService<IPackagingService>(container, c => new PackagingService(logger, contentService.Value, contentTypeService.Value, mediaService.Value, macroService.Value, dataTypeService.Value, fileService.Value, localizationService.Value, entityService.Value, userService.Value, scopeProvider, urlSegmentProviders, GetRepo<IAuditRepository>(c), GetRepo<IContentTypeRepository>(c), new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()))));
|
||||
var relationService = GetLazyService<IRelationService>(container, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo<IRelationRepository>(c), GetRepo<IRelationTypeRepository>(c)));
|
||||
var treeService = GetLazyService<IApplicationTreeService>(container, c => new ApplicationTreeService(logger, cache));
|
||||
var treeService = GetLazyService<IApplicationTreeService>(container, c => new ApplicationTreeService(logger, cache, typeLoader));
|
||||
var tagService = GetLazyService<ITagService>(container, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo<ITagRepository>(c)));
|
||||
var sectionService = GetLazyService<ISectionService>(container, c => new SectionService(userService.Value, treeService.Value, scopeProvider, cache));
|
||||
var redirectUrlService = GetLazyService<IRedirectUrlService>(container, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo<IRedirectUrlRepository>(c)));
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Umbraco.Tests.UI
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(typeof(macroTasks), Constants.Applications.Packages)]
|
||||
[TestCase(typeof(macroTasks), Constants.Applications.Settings)]
|
||||
[TestCase(typeof(CreatedPackageTasks), Constants.Applications.Packages)]
|
||||
public void Check_Assigned_Apps_For_Tasks(Type taskType, string app)
|
||||
{
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
|
||||
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
|
||||
<Compile Include="PublishedContent\NuCacheTests.cs" />
|
||||
<Compile Include="Services\ContentTypeServiceVariantsTests.cs" />
|
||||
<Compile Include="Testing\Objects\TestDataSource.cs" />
|
||||
<Compile Include="Published\PublishedSnapshotTestObjects.cs" />
|
||||
<Compile Include="Published\ModelTypeTests.cs" />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using LightInject;
|
||||
@@ -14,15 +13,12 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Stubs;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.Templates;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Tests.Web
|
||||
@@ -118,4 +114,4 @@ namespace Umbraco.Tests.Web
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -194,7 +194,9 @@
|
||||
return a.alias === "umbContent";
|
||||
});
|
||||
|
||||
contentApp.viewModel = _.omit(variant, 'apps');
|
||||
//The view model for the content app is simply the index of the variant being edited
|
||||
var variantIndex = vm.content.variants.indexOf(variant);
|
||||
contentApp.viewModel = variantIndex;
|
||||
|
||||
// make sure the same app it set to active in the new variant
|
||||
if(activeAppAlias) {
|
||||
|
||||
@@ -51,6 +51,15 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.umb-action {
|
||||
&.-opens-dialog {
|
||||
.menu-label:after {
|
||||
// adds an ellipsis (...) after the menu label for actions that open a dialog
|
||||
content: '\2026';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.umb-actions-child {
|
||||
|
||||
.umb-action {
|
||||
|
||||
+4
-5
@@ -16,24 +16,23 @@
|
||||
<umb-box-content>
|
||||
<div class="umb-insert-code-boxes">
|
||||
<div ng-if="model.allowedTypes.umbracoField" class="umb-insert-code-box" ng-click="vm.openPageFieldOverlay()">
|
||||
<div class="umb-insert-code-box__title"><localize key="template_insertPageField" /></div>
|
||||
<div class="umb-insert-code-box__title"><localize key="template_insertPageField" />...</div>
|
||||
<div class="umb-insert-code-box__description"><localize key="template_insertPageFieldDesc" /></div>
|
||||
</div>
|
||||
<div ng-if="model.allowedTypes.partial" class="umb-insert-code-box" ng-click="vm.openPartialOverlay()">
|
||||
<div class="umb-insert-code-box__title"><localize key="template_insertPartialView" /></div>
|
||||
<div class="umb-insert-code-box__title"><localize key="template_insertPartialView" />...</div>
|
||||
<div class="umb-insert-code-box__description">
|
||||
<localize key="template_insertPartialViewDesc" />
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="model.allowedTypes.macro" class="umb-insert-code-box" ng-click="vm.openMacroPicker()">
|
||||
<div class="umb-insert-code-box__title"><localize key="template_insertMacro" /></div>
|
||||
<div class="umb-insert-code-box__title"><localize key="template_insertMacro" />...</div>
|
||||
<div class="umb-insert-code-box__description">
|
||||
<localize key="template_insertMacroDesc" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="model.allowedTypes.dictionary" class="umb-insert-code-box" ng-click="vm.openDictionaryItemOverlay()">
|
||||
<div class="umb-insert-code-box__title"><localize key="template_insertDictionaryItem" /></div>
|
||||
<div class="umb-insert-code-box__title"><localize key="template_insertDictionaryItem" />...</div>
|
||||
<div class="umb-insert-code-box__description"><localize key="template_insertDictionaryItemDesc" /></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -376,7 +376,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
|
||||
var foundIndex = 0;
|
||||
|
||||
if ($scope.model.selection.length > 0) {
|
||||
for (i = 0; $scope.model.selection.length > i; i++) {
|
||||
for (var i = 0; $scope.model.selection.length > i; i++) {
|
||||
var selectedItem = $scope.model.selection[i];
|
||||
if (selectedItem.id === item.id) {
|
||||
found = true;
|
||||
|
||||
+1
-1
@@ -285,7 +285,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
|
||||
var foundIndex = 0;
|
||||
|
||||
if ($scope.model.selection.length > 0) {
|
||||
for (i = 0; $scope.model.selection.length > i; i++) {
|
||||
for (var i = 0; $scope.model.selection.length > i; i++) {
|
||||
var selectedItem = $scope.model.selection[i];
|
||||
if (selectedItem.id === item.id) {
|
||||
found = true;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<div class='umb-modalcolumn-body'>
|
||||
<ul class="umb-actions">
|
||||
<li data-element="action-{{action.alias}}" ng-click="executeMenuItem(action)" class="umb-action" ng-class="{sep:action.seperator}" ng-repeat="action in menuActions">
|
||||
<li data-element="action-{{action.alias}}" ng-click="executeMenuItem(action)" class="umb-action" ng-class="{sep:action.seperator, '-opens-dialog': action.opensDialog}" ng-repeat="action in menuActions">
|
||||
<a class="umb-action-link" prevent-default>
|
||||
<i class="icon icon-{{action.cssclass}}"></i>
|
||||
<span class="menu-label">{{action.name}}</span>
|
||||
@@ -13,4 +13,4 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@
|
||||
<umb-editor-menu
|
||||
data-element="editor-actions"
|
||||
current-node="menu.currentNode"
|
||||
current-section="{{menu.currentSection}}">
|
||||
current-section="{{menu.currentNode.section}}">
|
||||
</umb-editor-menu>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<!-- actions -->
|
||||
<ul class="dropdown-menu umb-actions" role="menu" aria-labelledby="dLabel">
|
||||
<li class="umb-action" ng-class="{sep:action.seperator}" ng-repeat="action in actions">
|
||||
<li class="umb-action" ng-class="{'sep':action.seperatorm, '-opens-dialog': action.opensDialog}" ng-repeat="action in actions">
|
||||
|
||||
<!-- How does this reference executeMenuItem() i really don't think that this is very clear -->
|
||||
<a prevent-default
|
||||
@@ -17,7 +17,6 @@
|
||||
<i class="icon icon-{{action.cssclass}}"></i>
|
||||
<span class="menu-label">{{action.name}}</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
label-key="contentTypeEditor_compositions"
|
||||
icon="icon-merge"
|
||||
action="openCompositionsDialog()"
|
||||
size="xs">
|
||||
size="xs"
|
||||
add-ellipsis="true">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
|
||||
@@ -3,33 +3,39 @@
|
||||
|
||||
function ContentAppContentController($scope, $timeout, serverValidationManager) {
|
||||
|
||||
//the contentApp's viewModel is actually the index of the variant being edited, not the variant itself.
|
||||
//if we make the viewModel the variant itself, we end up with a circular reference in the models which isn't ideal
|
||||
// (i.e. variant.apps[contentApp].viewModel = variant)
|
||||
//so instead since we already have access to the content, we can just get the variant directly by the index.
|
||||
|
||||
var vm = this;
|
||||
vm.loading = true;
|
||||
|
||||
function onInit() {
|
||||
vm.content = $scope.model.viewModel;
|
||||
//get the variant by index (see notes above)
|
||||
vm.content = $scope.content.variants[$scope.model.viewModel];
|
||||
serverValidationManager.notify();
|
||||
vm.loading = false;
|
||||
|
||||
//if this variant has a culture/language assigned, then we need to watch it since it will change
|
||||
//if the language drop down changes and we need to re-init
|
||||
if (vm.content.language) {
|
||||
$scope.$watch(function () {
|
||||
return vm.content.language.culture;
|
||||
}, function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
vm.loading = true;
|
||||
|
||||
//TODO: Can we minimize the flicker?
|
||||
$timeout(function () {
|
||||
onInit();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onInit();
|
||||
|
||||
//if this variant has a culture/language assigned, then we need to watch it since it will change
|
||||
//if the language drop down changes and we need to re-init
|
||||
if ($scope.model.viewModel.language) {
|
||||
$scope.$watch(function () {
|
||||
return $scope.model.viewModel.language.culture;
|
||||
}, function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
vm.loading = true;
|
||||
|
||||
//TODO: Can we minimize the flicker?
|
||||
$timeout(function () {
|
||||
onInit();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.Content.Apps.ContentController", ContentAppContentController);
|
||||
|
||||
@@ -22,12 +22,8 @@
|
||||
//determine a variant is 'dirty' (meaning it will show up as save-able) if it's
|
||||
// * the active one
|
||||
// * it's editor is in a $dirty state
|
||||
// * it's umbContent app viewModel (if any) is in a $dirty state
|
||||
// * it is in NotCreated state
|
||||
var contentApp = _.find(variant.apps, function(app) {
|
||||
return app.alias === "umbContent";
|
||||
});
|
||||
return (variant.active || variant.isDirty || (contentApp && contentApp.viewModel && contentApp.viewModel.isDirty));
|
||||
return (variant.active || variant.isDirty);
|
||||
}
|
||||
|
||||
function pristineVariantFilter(variant) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<a href="" class="umb-action-link" ng-click="showCreateFolder()">
|
||||
<i class="large icon icon-folder"></i>
|
||||
<span class="menu-label">
|
||||
<localize key="create_newFolder">New folder</localize>
|
||||
<localize key="create_newFolder">New folder</localize>...
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -20,7 +20,7 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes
|
||||
vm.page.menu.currentNode = null;
|
||||
vm.description = "";
|
||||
vm.showBackButton = true;
|
||||
|
||||
|
||||
vm.save = saveDictionary;
|
||||
vm.back = back;
|
||||
|
||||
@@ -102,9 +102,9 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function back() {
|
||||
$location.path("settings/dictionary/list");
|
||||
$location.path(vm.page.menu.currentSection + "/dictionary/list");
|
||||
}
|
||||
|
||||
$scope.$watch("vm.content.name", function (newVal, oldVal) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<a href="" ng-click="showCreateDocTypeCollection()" class="umb-action-link">
|
||||
<i class="large icon icon-thumbnail-list"></i>
|
||||
<span class="menu-label">
|
||||
Document Type Collection
|
||||
Document Type Collection...
|
||||
<!-- <localize key="content_documentType_collection">Document Type Collection</localize>-->
|
||||
</span>
|
||||
</a>
|
||||
@@ -33,7 +33,7 @@
|
||||
<li data-element="action-folder" ng-if="model.allowCreateFolder" class="umb-action">
|
||||
<a href="" ng-click="showCreateFolder()" class="umb-action-link">
|
||||
<i class="large icon icon-folder"></i>
|
||||
<span class="menu-label"><localize key="general_folder"></localize></span>
|
||||
<span class="menu-label"><localize key="general_folder"></localize>...</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"shortcuts_toggleListView",
|
||||
"shortcuts_toggleAllowAsRoot",
|
||||
"shortcuts_addChildNode",
|
||||
"shortcuts_addTemplate"
|
||||
"shortcuts_addTemplate",
|
||||
"shortcuts_toggleAllowCultureVariants"
|
||||
];
|
||||
|
||||
onInit();
|
||||
@@ -81,6 +82,7 @@
|
||||
vm.labels.allowAsRoot = values[11];
|
||||
vm.labels.addChildNode = values[12];
|
||||
vm.labels.addTemplate = values[13];
|
||||
vm.labels.allowCultureVariants = values[14];
|
||||
|
||||
var buttons = [
|
||||
{
|
||||
@@ -161,6 +163,10 @@
|
||||
{
|
||||
"description": vm.labels.addChildNode,
|
||||
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }]
|
||||
},
|
||||
{
|
||||
"description": vm.labels.allowCultureVariants,
|
||||
"keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "v" }]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+12
-2
@@ -23,7 +23,8 @@
|
||||
|
||||
vm.addChild = addChild;
|
||||
vm.removeChild = removeChild;
|
||||
vm.toggle = toggle;
|
||||
vm.toggleAllowAsRoot = toggleAllowAsRoot;
|
||||
vm.toggleAllowCultureVariants = toggleAllowCultureVariants;
|
||||
|
||||
/* ---------- INIT ---------- */
|
||||
|
||||
@@ -86,7 +87,7 @@
|
||||
/**
|
||||
* Toggle the $scope.model.allowAsRoot value to either true or false
|
||||
*/
|
||||
function toggle(){
|
||||
function toggleAllowAsRoot(){
|
||||
if($scope.model.allowAsRoot){
|
||||
$scope.model.allowAsRoot = false;
|
||||
return;
|
||||
@@ -95,6 +96,15 @@
|
||||
$scope.model.allowAsRoot = true;
|
||||
}
|
||||
|
||||
function toggleAllowCultureVariants() {
|
||||
if ($scope.model.allowCultureVariant) {
|
||||
$scope.model.allowCultureVariant = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.model.allowCultureVariant = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.DocumentType.PermissionsController", PermissionsController);
|
||||
|
||||
+22
-22
@@ -10,11 +10,10 @@
|
||||
<small><localize key="contentTypeEditor_allowAsRootDescription" /></small>
|
||||
</div>
|
||||
<div class="sub-view-column-right">
|
||||
<umb-toggle
|
||||
data-element="permissions-allow-as-root"
|
||||
checked="model.allowAsRoot"
|
||||
on-click="vm.toggle()"
|
||||
hotkey="alt+shift+r">
|
||||
<umb-toggle data-element="permissions-allow-as-root"
|
||||
checked="model.allowAsRoot"
|
||||
on-click="vm.toggleAllowAsRoot()"
|
||||
hotkey="alt+shift+r">
|
||||
</umb-toggle>
|
||||
</div>
|
||||
|
||||
@@ -28,14 +27,13 @@
|
||||
</div>
|
||||
|
||||
<div class="sub-view-column-right">
|
||||
<umb-child-selector
|
||||
selected-children="vm.selectedChildren"
|
||||
available-children="vm.contentTypes"
|
||||
parent-name="model.name"
|
||||
parent-icon="model.icon"
|
||||
parent-id="model.id"
|
||||
on-add="vm.addChild"
|
||||
on-remove="vm.removeChild">
|
||||
<umb-child-selector selected-children="vm.selectedChildren"
|
||||
available-children="vm.contentTypes"
|
||||
parent-name="model.name"
|
||||
parent-icon="model.icon"
|
||||
parent-id="model.id"
|
||||
on-add="vm.addChild"
|
||||
on-remove="vm.removeChild">
|
||||
</umb-child-selector>
|
||||
</div>
|
||||
|
||||
@@ -44,18 +42,20 @@
|
||||
<div class="sub-view-columns">
|
||||
|
||||
<div class="sub-view-column-left">
|
||||
<h5>Content Type Variation</h5>
|
||||
<small>Define the rules for how this content type's properties can be varied</small>
|
||||
</div>
|
||||
<div class="sub-view-column-right">
|
||||
<label class="checkbox no-indent">
|
||||
<input type="checkbox" ng-model="model.allowCultureVariant" />
|
||||
Allow varying by Culture
|
||||
</label>
|
||||
<h5><localize key="contentTypeEditor_variantsHeading" /></h5>
|
||||
<small><localize key="contentTypeEditor_variantsDescription" /></small>
|
||||
</div>
|
||||
|
||||
<div class="sub-view-column-right">
|
||||
<umb-toggle data-element="permissions-allow-culture-variant"
|
||||
checked="model.allowCultureVariant"
|
||||
on-click="vm.toggleAllowCultureVariants()"
|
||||
hotkey="alt+shift+v">
|
||||
</umb-toggle>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<umb-editor-container>
|
||||
<div class="umb-editor-sub-views" ng-if="!page.loading">
|
||||
<div id="sub-view-{{$index}}" ng-repeat="app in content.apps track by app.alias">
|
||||
<umb-editor-sub-view model="app" content="content" />
|
||||
<umb-editor-sub-view model="app" content="content" />
|
||||
</div>
|
||||
</div>
|
||||
</umb-editor-container>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<li class="umb-action">
|
||||
<a href="" class="umb-action-link" ng-click="showCreateFolder()">
|
||||
<i class="large icon icon-folder"></i>
|
||||
<span class="menu-label"><localize key="general_folder"></localize></span>
|
||||
<span class="menu-label"><localize key="general_folder"></localize>...</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
<li class="umb-action">
|
||||
<a href="" class="umb-action-link" ng-click="vm.showCreateFromSnippet()">
|
||||
<i class="large icon icon-article"></i>
|
||||
<span class="menu-label"><localize key="create_newPartialViewMacroFromSnippet">>New partial view macro from snippet</localize></span>
|
||||
<span class="menu-label"><localize key="create_newPartialViewMacroFromSnippet">>New partial view macro from snippet</localize>...</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="umb-action">
|
||||
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
|
||||
<i class="large icon icon-folder"></i>
|
||||
<span class="menu-label"><localize key="general_folder"></localize></span>
|
||||
<span class="menu-label"><localize key="general_folder"></localize>...</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
<li class="umb-action">
|
||||
<a href="" class="umb-action-link" ng-click="vm.showCreateFromSnippet()">
|
||||
<i class="large icon icon-article"></i>
|
||||
<span class="menu-label"><localize key="create_newPartialViewFromSnippet">New partial view from snippet</localize></span>
|
||||
<span class="menu-label"><localize key="create_newPartialViewFromSnippet">New partial view from snippet</localize>...</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="umb-action">
|
||||
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
|
||||
<i class="large icon icon-folder"></i>
|
||||
<span class="menu-label"><localize key="general_folder"></localize></span>
|
||||
<span class="menu-label"><localize key="general_folder"></localize>...</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
+10
-4
@@ -282,7 +282,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
});
|
||||
|
||||
/** Syncs the renderModel based on the actual model.value and returns a promise */
|
||||
function syncRenderModel() {
|
||||
function syncRenderModel(validate) {
|
||||
|
||||
var valueIds = $scope.model.value ? $scope.model.value.split(',') : [];
|
||||
|
||||
@@ -324,7 +324,10 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
|
||||
});
|
||||
|
||||
validate();
|
||||
if (validate) {
|
||||
validate();
|
||||
}
|
||||
|
||||
setSortingState($scope.renderModel);
|
||||
return $q.when(true);
|
||||
});
|
||||
@@ -344,7 +347,10 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
}
|
||||
}
|
||||
|
||||
validate();
|
||||
if (validate) {
|
||||
validate();
|
||||
}
|
||||
|
||||
setSortingState($scope.renderModel);
|
||||
return $q.when(true);
|
||||
}
|
||||
@@ -425,7 +431,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
}
|
||||
|
||||
function init() {
|
||||
syncRenderModel().then(function () {
|
||||
syncRenderModel(false).then(function () {
|
||||
//everything is loaded, start the watch on the model
|
||||
startWatch();
|
||||
subscribe();
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<li class="umb-action">
|
||||
<a href="" class="umb-action-link" ng-click="vm.showCreateFolder()">
|
||||
<i class="large icon icon-folder"></i>
|
||||
<span class="menu-label"><localize key="general_folder"></localize></span>
|
||||
<span class="menu-label"><localize key="general_folder"></localize>...</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
<key alias="move">Allow access to move a node</key>
|
||||
<key alias="protect">Allow access to set and change public access for a node</key>
|
||||
<key alias="publish">Allow access to publish a node</key>
|
||||
<key alias="unpublish">Allow access to unpublish a node</key>
|
||||
<key alias="rights">Allow access to change permissions for a node</key>
|
||||
<key alias="rollback">Allow access to roll back a node to a previous state</key>
|
||||
<key alias="sendtopublish">Allow access to send a node for approval before publishing</key>
|
||||
@@ -680,6 +681,7 @@
|
||||
<key alias="moveLineDown">Move Lines Down</key>
|
||||
<key alias="generalHeader">General</key>
|
||||
<key alias="editorHeader">Editor</key>
|
||||
<key alias="toggleAllowCultureVariants">Toggle allow culture variants</key>
|
||||
</area>
|
||||
<area alias="graphicheadline">
|
||||
<key alias="backgroundcolor">Background colour</key>
|
||||
@@ -1487,6 +1489,9 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="tabHasNoSortOrder">tab has no sort order</key>
|
||||
<key alias="compositionUsageHeading">Where is this composition used?</key>
|
||||
<key alias="compositionUsageSpecification">This composition is currently used in the composition of the following content types:</key>
|
||||
<key alias="variantsHeading">Allow varying by culture</key>
|
||||
<key alias="variantsDescription">Allow editors to create content of this type in different languages</key>
|
||||
<key alias="allowVaryByCulture">Allow varying by culture</key>
|
||||
</area>
|
||||
<area alias="modelsBuilder">
|
||||
<key alias="buildingModels">Building models</key>
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
<key alias="move">Allow access to move a node</key>
|
||||
<key alias="protect">Allow access to set and change public access for a node</key>
|
||||
<key alias="publish">Allow access to publish a node</key>
|
||||
<key alias="unpublish">Allow access to unpublish a node</key>
|
||||
<key alias="rights">Allow access to change permissions for a node</key>
|
||||
<key alias="rollback">Allow access to roll back a node to a previous state</key>
|
||||
<key alias="sendtopublish">Allow access to send a node for approval before publishing</key>
|
||||
@@ -700,6 +701,7 @@
|
||||
<key alias="moveLineDown">Move Lines Down</key>
|
||||
<key alias="generalHeader">General</key>
|
||||
<key alias="editorHeader">Editor</key>
|
||||
<key alias="toggleAllowCultureVariants">Toggle allow culture variants</key>
|
||||
</area>
|
||||
<area alias="graphicheadline">
|
||||
<key alias="backgroundcolor">Background color</key>
|
||||
@@ -1509,6 +1511,9 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="tabHasNoSortOrder">tab has no sort order</key>
|
||||
<key alias="compositionUsageHeading">Where is this composition used?</key>
|
||||
<key alias="compositionUsageSpecification">This composition is currently used in the composition of the following content types:</key>
|
||||
<key alias="variantsHeading">Allow varying by culture</key>
|
||||
<key alias="variantsDescription">Allow editors to create content of this type in different languages</key>
|
||||
<key alias="allowVaryByCulture">Allow varying by culture</key>
|
||||
</area>
|
||||
<area alias="languages">
|
||||
<key alias="addLanguage">Add language</key>
|
||||
|
||||
@@ -1124,7 +1124,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <param name="model">The content and variants to unpublish</param>
|
||||
/// <returns></returns>
|
||||
[EnsureUserPermissionForContent("model.Id", 'U')]
|
||||
[EnsureUserPermissionForContent("model.Id", 'Z')]
|
||||
[OutgoingEditorModelEvent]
|
||||
public ContentItemDisplay PostUnpublish(UnpublishContent model)
|
||||
{
|
||||
|
||||
@@ -119,66 +119,74 @@ namespace Umbraco.Web.Editors
|
||||
/// <param name="type">Type of content Type, eg documentType or mediaType</param>
|
||||
/// <param name="contentTypeId">Id of composition content type</param>
|
||||
/// <returns></returns>
|
||||
protected IEnumerable<EntityBasic> PerformGetWhereCompositionIsUsedInContentTypes(int contentTypeId,
|
||||
UmbracoObjectTypes type)
|
||||
protected IEnumerable<EntityBasic> PerformGetWhereCompositionIsUsedInContentTypes(int contentTypeId, UmbracoObjectTypes type)
|
||||
{
|
||||
IContentTypeComposition source = null;
|
||||
var id = 0;
|
||||
|
||||
//below is all ported from the old doc type editor and comes with the same weaknesses /insanity / magic
|
||||
if (contentTypeId > 0)
|
||||
{
|
||||
IContentTypeComposition source;
|
||||
|
||||
IContentTypeComposition[] allContentTypes;
|
||||
switch (type)
|
||||
{
|
||||
case UmbracoObjectTypes.DocumentType:
|
||||
source = Services.ContentTypeService.Get(contentTypeId);
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MediaType:
|
||||
source = Services.ContentTypeService.Get(contentTypeId);
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MemberType:
|
||||
source = Services.MemberTypeService.Get(contentTypeId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type));
|
||||
}
|
||||
|
||||
if (source == null)
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
|
||||
id = source.Id;
|
||||
}
|
||||
|
||||
IEnumerable<IContentTypeComposition> composedOf;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case UmbracoObjectTypes.DocumentType:
|
||||
if (contentTypeId > 0)
|
||||
{
|
||||
source = Services.ContentTypeService.Get(contentTypeId);
|
||||
if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
}
|
||||
allContentTypes = Services.ContentTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
|
||||
composedOf = Services.ContentTypeService.GetComposedOf(id);
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MediaType:
|
||||
if (contentTypeId > 0)
|
||||
{
|
||||
source = Services.ContentTypeService.Get(contentTypeId);
|
||||
if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
}
|
||||
allContentTypes = Services.ContentTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
|
||||
composedOf = Services.MediaTypeService.GetComposedOf(id);
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MemberType:
|
||||
if (contentTypeId > 0)
|
||||
{
|
||||
source = Services.MemberTypeService.Get(contentTypeId);
|
||||
if (source == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
}
|
||||
allContentTypes = Services.MemberTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
|
||||
composedOf = Services.MemberTypeService.GetComposedOf(id);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("The entity type was not a content type");
|
||||
throw new ArgumentOutOfRangeException(nameof(type));
|
||||
}
|
||||
|
||||
var contentTypesWhereCompositionIsUsed = source.GetWhereCompositionIsUsedInContentTypes(allContentTypes);
|
||||
return contentTypesWhereCompositionIsUsed
|
||||
.Select(x => Mapper.Map<IContentTypeComposition, EntityBasic>(x))
|
||||
.Select(x =>
|
||||
{
|
||||
//translate the name
|
||||
x.Name = TranslateItem(x.Name);
|
||||
EntityBasic TranslateName(EntityBasic e)
|
||||
{
|
||||
e.Name = TranslateItem(e.Name);
|
||||
return e;
|
||||
}
|
||||
|
||||
return x;
|
||||
})
|
||||
return composedOf
|
||||
.Select(Mapper.Map<IContentTypeComposition, EntityBasic>)
|
||||
.Select(TranslateName)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
protected string TranslateItem(string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (text.StartsWith("#") == false)
|
||||
return text;
|
||||
|
||||
@@ -131,13 +131,10 @@ namespace Umbraco.Web.Editors
|
||||
if (tree == null) continue; //shouldn't occur
|
||||
|
||||
var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute<SearchableTreeAttribute>(false);
|
||||
var treeAttribute = tree.GetTreeAttribute();
|
||||
|
||||
long total;
|
||||
|
||||
result[treeAttribute.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult
|
||||
result[tree.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult
|
||||
{
|
||||
Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out total),
|
||||
Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out var total),
|
||||
TreeAlias = searchableTree.Key,
|
||||
AppAlias = searchableTree.Value.AppAlias,
|
||||
JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName,
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Umbraco.Web.Models.Trees
|
||||
SeperatorBefore = false;
|
||||
Icon = legacyMenu.Icon;
|
||||
Action = legacyMenu;
|
||||
OpensDialog = legacyMenu.OpensDialog;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -71,6 +72,10 @@ namespace Umbraco.Web.Models.Trees
|
||||
|
||||
[DataMember(Name = "cssclass")]
|
||||
public string Icon { get; set; }
|
||||
|
||||
[DataMember(Name = "opensDialog")]
|
||||
public bool OpensDialog { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Umbraco.Web.Models.Trees
|
||||
{
|
||||
private static readonly string RootId = Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture);
|
||||
private bool _isGroup;
|
||||
private bool _isSingleNodeTree;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a group node for grouped multiple trees
|
||||
@@ -87,13 +88,15 @@ namespace Umbraco.Web.Models.Trees
|
||||
/// <param name="menuUrl"></param>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="children"></param>
|
||||
/// <param name="isSingleNodeTree"></param>
|
||||
/// <returns></returns>
|
||||
public static TreeRootNode CreateSingleTreeRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children)
|
||||
public static TreeRootNode CreateSingleTreeRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children, bool isSingleNodeTree = false)
|
||||
{
|
||||
return new TreeRootNode(nodeId, getChildNodesUrl, menuUrl)
|
||||
{
|
||||
Children = children,
|
||||
Name = title
|
||||
Name = title,
|
||||
_isSingleNodeTree = isSingleNodeTree
|
||||
};
|
||||
}
|
||||
|
||||
@@ -150,6 +153,6 @@ namespace Umbraco.Web.Models.Trees
|
||||
/// This is used in the UI to configure a full screen section/app
|
||||
/// </remarks>
|
||||
[DataMember(Name = "containsTrees")]
|
||||
public bool ContainsTrees => Children.Count > 0;
|
||||
public bool ContainsTrees => Children.Count > 0 || !_isSingleNodeTree;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1160,6 +1160,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
var pdatas = new List<PropertyData>();
|
||||
foreach (var pvalue in prop.Values)
|
||||
{
|
||||
// sanitize - properties should be ok but ... never knows
|
||||
if (!prop.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment))
|
||||
continue;
|
||||
|
||||
// note: at service level, invariant is 'null', but here invariant becomes 'string.Empty'
|
||||
var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
|
||||
if (value != null)
|
||||
@@ -1191,15 +1195,19 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
var cultureData = new Dictionary<string, CultureVariation>();
|
||||
|
||||
var names = content is IContent document
|
||||
// sanitize - names should be ok but ... never knows
|
||||
if (content.GetContentType().VariesByCulture())
|
||||
{
|
||||
var infos = content is IContent document
|
||||
? (published
|
||||
? document.PublishCultureInfos
|
||||
: document.CultureInfos)
|
||||
: content.CultureInfos;
|
||||
|
||||
foreach (var (culture, name) in names)
|
||||
{
|
||||
cultureData[culture] = new CultureVariation { Name = name.Name, Date = content.GetUpdateDate(culture) ?? DateTime.MinValue };
|
||||
foreach (var (culture, info) in infos)
|
||||
{
|
||||
cultureData[culture] = new CultureVariation { Name = info.Name, Date = content.GetUpdateDate(culture) ?? DateTime.MinValue };
|
||||
}
|
||||
}
|
||||
|
||||
//the dictionary that will be serialized
|
||||
|
||||
@@ -20,16 +20,18 @@ namespace Umbraco.Web.Services
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly CacheHelper _cache;
|
||||
private readonly TypeLoader _typeLoader;
|
||||
private Lazy<IEnumerable<ApplicationTree>> _allAvailableTrees;
|
||||
internal const string TreeConfigFileName = "trees.config";
|
||||
private static string _treeConfig;
|
||||
private static readonly object Locker = new object();
|
||||
private readonly Lazy<IReadOnlyCollection<IGrouping<string, string>>> _groupedTrees;
|
||||
|
||||
public ApplicationTreeService(ILogger logger, CacheHelper cache)
|
||||
public ApplicationTreeService(ILogger logger, CacheHelper cache, TypeLoader typeLoader)
|
||||
{
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
_typeLoader = typeLoader;
|
||||
_groupedTrees = new Lazy<IReadOnlyCollection<IGrouping<string, string>>>(InitGroupedTrees);
|
||||
}
|
||||
|
||||
@@ -443,19 +445,18 @@ namespace Umbraco.Web.Services
|
||||
/// </summary>
|
||||
private class LazyEnumerableTrees : IEnumerable<ApplicationTree>
|
||||
{
|
||||
public LazyEnumerableTrees()
|
||||
public LazyEnumerableTrees(TypeLoader typeLoader)
|
||||
{
|
||||
_lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
|
||||
{
|
||||
var added = new List<string>();
|
||||
|
||||
// Load all Controller Trees by attribute
|
||||
var types = Current.TypeLoader.GetTypesWithAttribute<TreeController, TreeAttribute>(); // fixme inject
|
||||
var types = typeLoader.GetTypesWithAttribute<TreeController, TreeAttribute>(); // fixme inject
|
||||
//convert them to ApplicationTree instances
|
||||
var items = types
|
||||
.Select(x =>
|
||||
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
|
||||
.Select(x => new ApplicationTree(x.Item2.Initialize, x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen, x.Item1.GetFullNameWithAssembly()))
|
||||
.Select(x => (tree: x, treeAttribute: x.GetCustomAttributes<TreeAttribute>(false).Single()))
|
||||
.Select(x => new ApplicationTree(x.treeAttribute.Initialize, x.treeAttribute.SortOrder, x.treeAttribute.ApplicationAlias, x.treeAttribute.Alias, x.treeAttribute.Title, x.treeAttribute.IconClosed, x.treeAttribute.IconOpen, x.tree.GetFullNameWithAssembly()))
|
||||
.ToArray();
|
||||
|
||||
added.AddRange(items.Select(x => x.Alias));
|
||||
@@ -465,7 +466,7 @@ namespace Umbraco.Web.Services
|
||||
}
|
||||
|
||||
private readonly Lazy<IEnumerable<ApplicationTree>> _lazyTrees;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Umbraco.Web.Trees
|
||||
var groupedTrees = Services.ApplicationTreeService.GetGroupedApplicationTrees(application, onlyInitialized);
|
||||
var allTrees = groupedTrees.Values.SelectMany(x => x).ToList();
|
||||
|
||||
if (string.IsNullOrEmpty(tree) == false || allTrees.Count <= 1)
|
||||
if (string.IsNullOrEmpty(tree) == false || allTrees.Count == 1)
|
||||
{
|
||||
var apptree = !tree.IsNullOrWhiteSpace()
|
||||
? allTrees.FirstOrDefault(x => x.Alias == tree)
|
||||
@@ -171,12 +171,15 @@ namespace Umbraco.Web.Trees
|
||||
throw new InvalidOperationException("Could not create root node for tree " + configTree.Alias);
|
||||
}
|
||||
|
||||
var treeAttribute = configTree.GetTreeAttribute();
|
||||
|
||||
var sectionRoot = TreeRootNode.CreateSingleTreeRoot(
|
||||
rootId,
|
||||
rootNode.Result.ChildNodesUrl,
|
||||
rootNode.Result.MenuUrl,
|
||||
rootNode.Result.Name,
|
||||
byControllerAttempt.Result);
|
||||
byControllerAttempt.Result,
|
||||
treeAttribute.IsSingleNodeTree);
|
||||
|
||||
//assign the route path based on the root node, this means it will route there when the section is navigated to
|
||||
//and no dashboards will be available for this section
|
||||
|
||||
@@ -48,28 +48,6 @@ namespace Umbraco.Web.Trees
|
||||
return tree.GetRuntimeType().GetTreeAttribute();
|
||||
}
|
||||
|
||||
internal static string GetRootNodeDisplayName(this TreeAttribute attribute, ILocalizedTextService textService)
|
||||
{
|
||||
var label = $"[{attribute.Alias}]";
|
||||
|
||||
// try to look up a the localized tree header matching the tree alias
|
||||
var localizedLabel = textService.Localize("treeHeaders/" + attribute.Alias);
|
||||
|
||||
// if the localizedLabel returns [alias] then return the title attribute from the trees.config file, if it's defined
|
||||
if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(attribute.Title) == false)
|
||||
label = attribute.Title;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the localizedLabel translated into something that's not just [alias], so use the translation
|
||||
label = localizedLabel;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
internal static Attempt<Type> TryGetControllerTree(this ApplicationTree appTree)
|
||||
{
|
||||
//get reference to all TreeApiControllers
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Web.Trees
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Dictionary)]
|
||||
[Mvc.PluginController("UmbracoTrees")]
|
||||
[CoreTree(TreeGroup = Constants.Trees.Groups.Settings)]
|
||||
[Tree(Constants.Applications.Translation, Constants.Trees.Dictionary, null, sortOrder: 0)]
|
||||
[Tree(Constants.Applications.Translation, Constants.Trees.Dictionary, null)]
|
||||
public class DictionaryTreeController : TreeController
|
||||
{
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
|
||||
@@ -28,13 +28,15 @@ namespace Umbraco.Web.Trees
|
||||
/// <param name="iconOpen">The icon open.</param>
|
||||
/// <param name="initialize">if set to <c>true</c> [initialize].</param>
|
||||
/// <param name="sortOrder">The sort order.</param>
|
||||
/// <param name="isSingleNodeTree">Flag to define if this tree is a single node tree (will never contain child nodes, full screen app)</param>
|
||||
public TreeAttribute(string appAlias,
|
||||
string alias,
|
||||
string title,
|
||||
string iconClosed = "icon-folder",
|
||||
string iconOpen = "icon-folder-open",
|
||||
bool initialize = true,
|
||||
int sortOrder = 0)
|
||||
int sortOrder = 0,
|
||||
bool isSingleNodeTree = false)
|
||||
{
|
||||
ApplicationAlias = appAlias;
|
||||
Alias = alias;
|
||||
@@ -43,6 +45,7 @@ namespace Umbraco.Web.Trees
|
||||
IconOpen = iconOpen;
|
||||
Initialize = initialize;
|
||||
SortOrder = sortOrder;
|
||||
IsSingleNodeTree = isSingleNodeTree;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,5 +57,10 @@ namespace Umbraco.Web.Trees
|
||||
public string IconOpen { get; private set; }
|
||||
public bool Initialize { get; private set; }
|
||||
public int SortOrder { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app)
|
||||
/// </summary>
|
||||
public bool IsSingleNodeTree { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Umbraco.Web.Trees
|
||||
public abstract class TreeController : TreeControllerBase
|
||||
{
|
||||
private TreeAttribute _attribute;
|
||||
private string _rootNodeDisplayName;
|
||||
|
||||
protected TreeController()
|
||||
{
|
||||
@@ -20,9 +21,9 @@ namespace Umbraco.Web.Trees
|
||||
/// The name to display on the root node
|
||||
/// </summary>
|
||||
public override string RootNodeDisplayName
|
||||
{
|
||||
get { return _attribute.GetRootNodeDisplayName(Services.TextService); }
|
||||
}
|
||||
=> _rootNodeDisplayName
|
||||
?? (_rootNodeDisplayName = Services.ApplicationTreeService.GetByAlias(_attribute.Alias)
|
||||
?.GetRootNodeDisplayName(Services.TextService));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current tree alias from the attribute assigned to it.
|
||||
|
||||
@@ -10,7 +10,7 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Users)]
|
||||
[Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 0)]
|
||||
[Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 0, isSingleNodeTree: true)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class UserTreeController : TreeController
|
||||
|
||||
@@ -174,6 +174,7 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
public string Alias { get; set; }
|
||||
public string JsFunctionName { get; set; }
|
||||
public string JsSource { get; set; }
|
||||
public bool OpensDialog { get; set; }
|
||||
|
||||
public PlaceboAction() { }
|
||||
public PlaceboAction(IAction legacyAction)
|
||||
@@ -185,6 +186,7 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
Alias = legacyAction.Alias;
|
||||
JsFunctionName = legacyAction.JsFunctionName;
|
||||
JsSource = legacyAction.JsSource;
|
||||
OpensDialog = legacyAction.OpensDialog;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
get { return ""; }
|
||||
}
|
||||
|
||||
public bool OpensDialog => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
public string Alias { get; private set; }
|
||||
public string JsFunctionName { get; private set; }
|
||||
public string JsSource { get; private set; }
|
||||
public bool OpensDialog => true;
|
||||
|
||||
public ActionCreateBlueprintFromContent()
|
||||
{
|
||||
|
||||
@@ -77,6 +77,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
get { return string.Empty; }
|
||||
}
|
||||
|
||||
public bool OpensDialog => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
|
||||
public bool CanBePermissionAssigned => false;
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using System;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.CodeAnnotations;
|
||||
|
||||
namespace Umbraco.Web._Legacy.Actions
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This action is invoked when a document is being unpublished
|
||||
/// </summary>
|
||||
[ActionMetadata(Constants.Conventions.PermissionCategories.ContentCategory)]
|
||||
public class ActionUnpublish : IAction
|
||||
{
|
||||
//create singleton
|
||||
@@ -15,68 +16,17 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
private static readonly ActionUnpublish m_instance = new ActionUnpublish();
|
||||
#pragma warning restore 612,618
|
||||
|
||||
public static ActionUnpublish Instance
|
||||
{
|
||||
get { return m_instance; }
|
||||
}
|
||||
public static ActionUnpublish Instance => m_instance;
|
||||
|
||||
#region IAction Members
|
||||
public char Letter => 'Z';
|
||||
public string JsFunctionName => "";
|
||||
public string JsSource => null;
|
||||
public string Alias => "unpublish";
|
||||
public string Icon => "circle-dotted";
|
||||
public bool ShowInNotifier => false;
|
||||
public bool CanBePermissionAssigned => true;
|
||||
public bool OpensDialog => false;
|
||||
|
||||
public char Letter
|
||||
{
|
||||
get
|
||||
{
|
||||
return 'Z';
|
||||
}
|
||||
}
|
||||
|
||||
public string JsFunctionName
|
||||
{
|
||||
get
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public string JsSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string Alias
|
||||
{
|
||||
get
|
||||
{
|
||||
return "unpublish";
|
||||
}
|
||||
}
|
||||
|
||||
public string Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
return "circle-dotted";
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowInNotifier
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool CanBePermissionAssigned
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -77,6 +77,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpensDialog => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public bool OpensDialog => false;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,9 @@ namespace Umbraco.Web._Legacy.Actions
|
||||
/// A path to a supporting JavaScript file for the IAction. A script tag will be rendered out with the reference to the JavaScript file.
|
||||
/// </summary>
|
||||
string JsSource { get; }
|
||||
/// <summary>
|
||||
/// Whether or not the action opens a dialog when invoked
|
||||
/// </summary>
|
||||
bool OpensDialog { get; }
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -78,6 +78,8 @@ namespace umbraco.cms.presentation.developer.RelationTypes.TreeMenu
|
||||
get { return "javascript:actionDeleteRelationType(UmbClientMgr.mainTree().getActionNode().nodeId,UmbClientMgr.mainTree().getActionNode().nodeName);"; }
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -78,6 +78,8 @@ namespace umbraco.cms.presentation.developer.RelationTypes.TreeMenu
|
||||
get { return "javascript:actionNewRelationType();"; }
|
||||
}
|
||||
|
||||
public bool OpensDialog => true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user