Merge branch 'temp8' into temp8-223-scheduled-publishing
This commit is contained in:
@@ -159,6 +159,9 @@ namespace Umbraco.Core.Composing
|
||||
public static IPublishedValueFallback PublishedValueFallback
|
||||
=> _publishedValueFallback ?? Container.GetInstance<IPublishedValueFallback>() ?? new NoopPublishedValueFallback();
|
||||
|
||||
public static IVariationContextAccessor VariationContextAccessor
|
||||
=> Container.GetInstance<IVariationContextAccessor>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
private void CreateLanguageData()
|
||||
{
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefaultVariantLanguage = true });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefault = true });
|
||||
}
|
||||
|
||||
private void CreateContentChildTypeData()
|
||||
|
||||
@@ -107,18 +107,35 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
|
||||
Chain<UserForeignKeys>("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}"); // shannon added that one - let's keep it as the default path
|
||||
//Chain<AddTypedLabels>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}"); // stephan added that one = merge conflict, remove,
|
||||
Chain<AddTypedLabels>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but it after shannon's, with a new target state,
|
||||
Chain<AddTypedLabels>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but add it after shannon's, with a new target state,
|
||||
Add<UserForeignKeys>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}", "{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // and provide a path out of the conflict state
|
||||
// resume at {4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4} ...
|
||||
|
||||
Chain<ContentVariationMigration>("{1350617A-4930-4D61-852F-E3AA9E692173}");
|
||||
Chain<UpdateUmbracoConsent>("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0
|
||||
//Chain<FallbackLanguage>("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); // andy added that one = merge conflict, remove
|
||||
Chain<AddRelationTypeForMediaFolderOnDelete>("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}"); // from 7.12.0
|
||||
Chain<IncreaseLanguageIsoCodeColumnLength>("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0
|
||||
Chain<RenameTrueFalseField>("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0
|
||||
Chain<SetDefaultTagsStorageType>("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0
|
||||
Chain<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}");
|
||||
Chain<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
//Chain<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); // stephan added that one = merge conflict, remove
|
||||
|
||||
Chain<FallbackLanguage>("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // add andy's after others, with a new target state
|
||||
From("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // and provide a path out of andy's
|
||||
.CopyChain("{39E5B1F7-A50B-437E-B768-1723AEC45B65}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}", "{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // to next
|
||||
// resume at {8B14CEBD-EE47-4AAD-A841-93551D917F11} ...
|
||||
|
||||
Chain<UpdateDefaultMandatoryLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // add stephan's after others, with a new target state
|
||||
From("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}") // and provide a path out of stephan's
|
||||
.Chain<FallbackLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // to next
|
||||
// resume at {5F4597F4-A4E0-4AFE-90B5-6D2F896830EB} ...
|
||||
|
||||
//Chain<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
Chain<RefactorVariantsModel>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
From("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}")
|
||||
.Chain<FallbackLanguage>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
// resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}...
|
||||
|
||||
//FINAL
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new, self-joined field to umbracoLanguages to hold the fall-back language for
|
||||
/// a given language.
|
||||
/// </summary>
|
||||
public class FallbackLanguage : MigrationBase
|
||||
{
|
||||
public FallbackLanguage(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.Language) && x.ColumnName.InvariantEquals("fallbackLanguageId")) == false)
|
||||
AddColumn<LanguageDto>("fallbackLanguageId");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
var defaultId = int.MaxValue;
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
if (dto.IsDefaultVariantLanguage)
|
||||
if (dto.IsDefault)
|
||||
{
|
||||
defaultId = dto.Id;
|
||||
break;
|
||||
@@ -38,8 +38,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
// update, so that language with that id is now default and mandatory
|
||||
var updateDefault = Sql()
|
||||
.Update<LanguageDto>(u => u
|
||||
.Set(x => x.IsDefaultVariantLanguage, true)
|
||||
.Set(x => x.Mandatory, true))
|
||||
.Set(x => x.IsDefault, true)
|
||||
.Set(x => x.IsMandatory, true))
|
||||
.Where<LanguageDto>(x => x.Id == defaultId);
|
||||
|
||||
Database.Execute(updateDefault);
|
||||
|
||||
@@ -4,34 +4,54 @@ using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a language.
|
||||
/// </summary>
|
||||
public interface ILanguage : IEntity, IRememberBeingDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Iso Code for the Language
|
||||
/// Gets or sets the ISO code of the language.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
string IsoCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Culture Name for the Language
|
||||
/// Gets or sets the culture name of the language.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
string CultureName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CultureInfo"/> object for the current Language
|
||||
/// Gets the <see cref="CultureInfo"/> object for the language.
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
CultureInfo CultureInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines if this language is the default variant language when language variants are in use
|
||||
/// Gets or sets a value indicating whether the language is the default language.
|
||||
/// </summary>
|
||||
bool IsDefaultVariantLanguage { get; set; }
|
||||
[DataMember]
|
||||
bool IsDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, a variant node cannot be published unless this language variant is created
|
||||
/// Gets or sets a value indicating whether the language is mandatory.
|
||||
/// </summary>
|
||||
bool Mandatory { get; set; }
|
||||
/// <remarks>
|
||||
/// <para>When a language is mandatory, a multi-lingual document cannot be published
|
||||
/// without that language being published, and unpublishing that language unpublishes
|
||||
/// the entire document.</para>
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
bool IsMandatory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of a fallback language.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The fallback language can be used in multi-lingual scenarios, to help
|
||||
/// define fallback strategies when a value does not exist for a requested language.</para>
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
int? FallbackLanguageId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Core.Models
|
||||
private string _cultureName;
|
||||
private bool _isDefaultVariantLanguage;
|
||||
private bool _mandatory;
|
||||
private int? _fallbackLanguageId;
|
||||
|
||||
public Language(string isoCode)
|
||||
{
|
||||
@@ -30,13 +31,12 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.IsoCode);
|
||||
public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.CultureName);
|
||||
public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsDefaultVariantLanguage);
|
||||
public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.Mandatory);
|
||||
public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsDefault);
|
||||
public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsMandatory);
|
||||
public readonly PropertyInfo FallbackLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, int?>(x => x.FallbackLanguageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Iso Code for the Language
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string IsoCode
|
||||
{
|
||||
@@ -44,9 +44,7 @@ namespace Umbraco.Core.Models
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Culture Name for the Language
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string CultureName
|
||||
{
|
||||
@@ -54,22 +52,29 @@ namespace Umbraco.Core.Models
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CultureInfo"/> object for the current Language
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode);
|
||||
|
||||
public bool IsDefaultVariantLanguage
|
||||
/// <inheritdoc />
|
||||
public bool IsDefault
|
||||
{
|
||||
get => _isDefaultVariantLanguage;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector);
|
||||
}
|
||||
|
||||
public bool Mandatory
|
||||
/// <inheritdoc />
|
||||
public bool IsMandatory
|
||||
{
|
||||
get => _mandatory;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? FallbackLanguageId
|
||||
{
|
||||
get => _fallbackLanguageId;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, Ps.Value.FallbackLanguageSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the built-in fallback policies.
|
||||
/// </summary>
|
||||
public struct Fallback : IEnumerable<int>
|
||||
{
|
||||
private readonly int[] _values;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Fallback"/> struct with values.
|
||||
/// </summary>
|
||||
private Fallback(int[] values)
|
||||
{
|
||||
_values = values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ordered set of fallback policies.
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
public static Fallback To(params int[] values) => new Fallback(values);
|
||||
|
||||
/// <summary>
|
||||
/// Do not fallback.
|
||||
/// </summary>
|
||||
public const int None = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Fallback to default value.
|
||||
/// </summary>
|
||||
public const int DefaultValue = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fallback to default value policy.
|
||||
/// </summary>
|
||||
public static Fallback ToDefaultValue => new Fallback(new[] { DefaultValue });
|
||||
|
||||
/// <summary>
|
||||
/// Fallback to other languages.
|
||||
/// </summary>
|
||||
public const int Language = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fallback to language policy.
|
||||
/// </summary>
|
||||
public static Fallback ToLanguage => new Fallback(new[] { Language });
|
||||
|
||||
/// <summary>
|
||||
/// Fallback to tree ancestors.
|
||||
/// </summary>
|
||||
public const int Ancestors = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fallback to tree ancestors policy.
|
||||
/// </summary>
|
||||
public static Fallback ToAncestors => new Fallback(new[] { Ancestors });
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<int> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<int>)_values ?? Array.Empty<int>()).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,122 @@
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a fallback strategy for getting <see cref="IPublishedElement"/> values.
|
||||
/// </summary>
|
||||
// fixme - IPublishedValueFallback is still WorkInProgress
|
||||
// todo - properly document methods, etc
|
||||
// todo - understand caching vs fallback (recurse etc)
|
||||
public interface IPublishedValueFallback
|
||||
{
|
||||
// note that at property level, property.GetValue() does NOT implement fallback, and one has
|
||||
// to get property.Value() or property.Value<T>() to trigger fallback
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a property.
|
||||
/// </summary>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever property.Value(culture, segment, defaultValue) is called, and
|
||||
/// property.HasValue(culture, segment) is false.</para>
|
||||
/// <para>It can only fallback at property level (no recurse).</para>
|
||||
/// <para>At property level, property.GetValue() does *not* implement fallback, and one has to
|
||||
/// get property.Value() or property.Value{T}() to trigger fallback.</para>
|
||||
/// <para>Note that <paramref name="culture"/> and <paramref name="segment"/> may not be contextualized,
|
||||
/// so the variant context should be used to contextualize them (see our default implementation in
|
||||
/// the web project.</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value);
|
||||
|
||||
// this method is called whenever property.Value(culture, segment, defaultValue) is called, and
|
||||
// property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a property.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever property.Value{T}(culture, segment, defaultValue) is called, and
|
||||
/// property.HasValue(culture, segment) is false.</para>
|
||||
/// <para>It can only fallback at property level (no recurse).</para>
|
||||
/// <para>At property level, property.GetValue() does *not* implement fallback, and one has to
|
||||
/// get property.Value() or property.Value{T}() to trigger fallback.</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value);
|
||||
|
||||
object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue);
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a published element property.
|
||||
/// </summary>
|
||||
/// <param name="content">The published element.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever getting the property value for the specified alias, culture and
|
||||
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
|
||||
/// <para>It can only fallback at element level (no recurse).</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
|
||||
|
||||
// this method is called whenever property.Value<T>(culture, segment, defaultValue) is called, and
|
||||
// property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a published element property.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="content">The published element.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever getting the property value for the specified alias, culture and
|
||||
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
|
||||
/// <para>It can only fallback at element level (no recurse).</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
|
||||
|
||||
T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue);
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a published content property.
|
||||
/// </summary>
|
||||
/// <param name="content">The published element.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever getting the property value for the specified alias, culture and
|
||||
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
|
||||
|
||||
// these methods to be called whenever getting the property value for the specified alias, culture and segment,
|
||||
// either returned no property at all, or a property that does not HasValue for the specified culture and segment.
|
||||
|
||||
object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue);
|
||||
|
||||
T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue);
|
||||
|
||||
object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse);
|
||||
|
||||
T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse);
|
||||
/// <summary>
|
||||
/// Tries to get a fallback value for a published content property.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value.</typeparam>
|
||||
/// <param name="content">The published element.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <param name="fallback">A fallback strategy.</param>
|
||||
/// <param name="defaultValue">An optional default value.</param>
|
||||
/// <param name="value">The fallback value.</param>
|
||||
/// <returns>A value indicating whether a fallback value could be provided.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This method is called whenever getting the property value for the specified alias, culture and
|
||||
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
|
||||
/// </remarks>
|
||||
bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,17 +49,24 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
/// <param name="modelTypes">The model types map.</param>
|
||||
/// <returns>The actual Clr type.</returns>
|
||||
public static Type Map(Type type, Dictionary<string, Type> modelTypes)
|
||||
=> Map(type, modelTypes, false);
|
||||
|
||||
public static Type Map(Type type, Dictionary<string, Type> modelTypes, bool dictionaryIsInvariant)
|
||||
{
|
||||
// it may be that senders forgot to send an invariant dictionary (garbage-in)
|
||||
if (!dictionaryIsInvariant)
|
||||
modelTypes = new Dictionary<string, Type>(modelTypes, StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
if (type is ModelType modelType)
|
||||
{
|
||||
if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out Type actualType))
|
||||
if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out var actualType))
|
||||
return actualType;
|
||||
throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\".");
|
||||
}
|
||||
|
||||
if (type is ModelTypeArrayType arrayType)
|
||||
{
|
||||
if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out Type actualType))
|
||||
if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out var actualType))
|
||||
return actualType.MakeArrayType();
|
||||
throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\".");
|
||||
}
|
||||
@@ -70,7 +77,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
if (def == null)
|
||||
throw new InvalidOperationException("panic");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes)).ToArray();
|
||||
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
|
||||
return def.MakeGenericType(args);
|
||||
}
|
||||
|
||||
@@ -81,7 +88,14 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
/// <param name="map">The model types map.</param>
|
||||
/// <returns>The actual Clr type name.</returns>
|
||||
public static string MapToName(Type type, Dictionary<string, string> map)
|
||||
=> MapToName(type, map, false);
|
||||
|
||||
private static string MapToName(Type type, Dictionary<string, string> map, bool dictionaryIsInvariant)
|
||||
{
|
||||
// it may be that senders forgot to send an invariant dictionary (garbage-in)
|
||||
if (!dictionaryIsInvariant)
|
||||
map = new Dictionary<string, string>(map, StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
if (type is ModelType modelType)
|
||||
{
|
||||
if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName))
|
||||
@@ -102,7 +116,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
if (def == null)
|
||||
throw new InvalidOperationException("panic");
|
||||
|
||||
var args = type.GetGenericArguments().Select(x => MapToName(x, map)).ToArray();
|
||||
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
|
||||
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
|
||||
return defFullName + "<" + string.Join(", ", args) + ">";
|
||||
}
|
||||
|
||||
@@ -9,21 +9,45 @@
|
||||
public class NoopPublishedValueFallback : IPublishedValueFallback
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue) => defaultValue;
|
||||
public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue) => defaultValue;
|
||||
public bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue) => defaultValue;
|
||||
public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue) => defaultValue;
|
||||
public bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse) => defaultValue;
|
||||
public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse) => defaultValue;
|
||||
public bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
public static class VariationContextAccessorExtensions
|
||||
{
|
||||
public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment)
|
||||
{
|
||||
if (culture != null && segment != null) return;
|
||||
|
||||
// use context values
|
||||
var publishedVariationContext = variationContextAccessor?.VariationContext;
|
||||
if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : "";
|
||||
if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,33 +10,51 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
{
|
||||
public const string TableName = Constants.DatabaseSchema.Tables.Language;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the language.
|
||||
/// </summary>
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(IdentitySeed = 2)]
|
||||
public short Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ISO code of the language.
|
||||
/// </summary>
|
||||
[Column("languageISOCode")]
|
||||
[Index(IndexTypes.UniqueNonClustered)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(14)]
|
||||
public string IsoCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the culture name of the language.
|
||||
/// </summary>
|
||||
[Column("languageCultureName")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(100)]
|
||||
public string CultureName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines if this language is the default variant language when language variants are in use
|
||||
/// Gets or sets a value indicating whether the language is the default language.
|
||||
/// </summary>
|
||||
[Column("isDefaultVariantLang")]
|
||||
[Constraint(Default = "0")]
|
||||
public bool IsDefaultVariantLanguage { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, a variant node cannot be published unless this language variant is created
|
||||
/// Gets or sets a value indicating whether the language is mandatory.
|
||||
/// </summary>
|
||||
[Column("mandatory")]
|
||||
[Constraint(Default = "0")]
|
||||
public bool Mandatory { get; set; }
|
||||
public bool IsMandatory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of a fallback language.
|
||||
/// </summary>
|
||||
[Column("fallbackLanguageId")]
|
||||
[ForeignKey(typeof(LanguageDto), Column = "id")]
|
||||
[Index(IndexTypes.NonClustered)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public int? FallbackLanguageId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
CultureName = dto.CultureName,
|
||||
Id = dto.Id,
|
||||
IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage,
|
||||
Mandatory = dto.Mandatory
|
||||
IsDefault = dto.IsDefault,
|
||||
IsMandatory = dto.IsMandatory,
|
||||
FallbackLanguageId = dto.FallbackLanguageId
|
||||
};
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
@@ -27,12 +28,15 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
CultureName = entity.CultureName,
|
||||
IsoCode = entity.IsoCode,
|
||||
IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage,
|
||||
Mandatory = entity.Mandatory
|
||||
IsDefault = entity.IsDefault,
|
||||
IsMandatory = entity.IsMandatory,
|
||||
FallbackLanguageId = entity.FallbackLanguageId
|
||||
};
|
||||
|
||||
if (entity.HasIdentity)
|
||||
{
|
||||
dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var sqlClause = GetBaseQuery(false);
|
||||
var translator = new SqlTranslator<ILanguage>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
return Database.Fetch<LanguageDto>(sql).Select(ConvertFromDto);
|
||||
var dtos = Database.Fetch<LanguageDto>(sql);
|
||||
return dtos.Select(ConvertFromDto).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -115,10 +116,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
return list;
|
||||
}
|
||||
|
||||
protected override Guid NodeObjectTypeId
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
protected override Guid NodeObjectTypeId => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -133,15 +131,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
((EntityBase) entity).AddingEntity();
|
||||
|
||||
// deal with entity becoming the new default entity
|
||||
if (entity.IsDefaultVariantLanguage)
|
||||
if (entity.IsDefault)
|
||||
{
|
||||
// set all other entities to non-default
|
||||
// safe (no race cond) because the service locks languages
|
||||
var setAllDefaultToFalse = Sql()
|
||||
.Update<LanguageDto>(u => u.Set(x => x.IsDefaultVariantLanguage, false));
|
||||
.Update<LanguageDto>(u => u.Set(x => x.IsDefault, false));
|
||||
Database.Execute(setAllDefaultToFalse);
|
||||
}
|
||||
;
|
||||
|
||||
// fallback cycles are detected at service level
|
||||
|
||||
// insert
|
||||
var dto = LanguageFactory.BuildDto(entity);
|
||||
var id = Convert.ToInt32(Database.Insert(dto));
|
||||
@@ -157,14 +157,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
((EntityBase) entity).UpdatingEntity();
|
||||
|
||||
if (entity.IsDefaultVariantLanguage)
|
||||
if (entity.IsDefault)
|
||||
{
|
||||
// deal with entity becoming the new default entity
|
||||
|
||||
// set all other entities to non-default
|
||||
// safe (no race cond) because the service locks languages
|
||||
var setAllDefaultToFalse = Sql()
|
||||
.Update<LanguageDto>(u => u.Set(x => x.IsDefaultVariantLanguage, false));
|
||||
.Update<LanguageDto>(u => u.Set(x => x.IsDefault, false));
|
||||
Database.Execute(setAllDefaultToFalse);
|
||||
}
|
||||
else
|
||||
@@ -174,13 +174,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var selectDefaultId = Sql()
|
||||
.Select<LanguageDto>(x => x.Id)
|
||||
.From<LanguageDto>()
|
||||
.Where<LanguageDto>(x => x.IsDefaultVariantLanguage);
|
||||
.Where<LanguageDto>(x => x.IsDefault);
|
||||
|
||||
var defaultId = Database.ExecuteScalar<int>(selectDefaultId);
|
||||
if (entity.Id == defaultId)
|
||||
throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead.");
|
||||
}
|
||||
|
||||
// fallback cycles are detected at service level
|
||||
|
||||
// update
|
||||
var dto = LanguageFactory.BuildDto(entity);
|
||||
Database.Update(dto);
|
||||
@@ -195,12 +197,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var selectDefaultId = Sql()
|
||||
.Select<LanguageDto>(x => x.Id)
|
||||
.From<LanguageDto>()
|
||||
.Where<LanguageDto>(x => x.IsDefaultVariantLanguage);
|
||||
.Where<LanguageDto>(x => x.IsDefault);
|
||||
|
||||
var defaultId = Database.ExecuteScalar<int>(selectDefaultId);
|
||||
if (entity.Id == defaultId)
|
||||
throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode}).");
|
||||
|
||||
// We need to remove any references to the language if it's being used as a fall-back from other ones
|
||||
var clearFallbackLanguage = Sql()
|
||||
.Update<LanguageDto>(u => u
|
||||
.Set(x => x.FallbackLanguageId, null))
|
||||
.Where<LanguageDto>(x => x.FallbackLanguageId == entity.Id);
|
||||
|
||||
Database.Execute(clearFallbackLanguage);
|
||||
|
||||
// delete
|
||||
base.PersistDeletedItem(entity);
|
||||
}
|
||||
@@ -212,7 +222,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var entity = LanguageFactory.BuildEntity(dto);
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
public ILanguage GetByIsoCode(string isoCode)
|
||||
{
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
@@ -271,7 +281,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
// get all cached, non-cloned
|
||||
var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList();
|
||||
var language = languages.FirstOrDefault(x => x.IsDefaultVariantLanguage);
|
||||
var language = languages.FirstOrDefault(x => x.IsDefault);
|
||||
if (language != null) return language;
|
||||
|
||||
// this is an anomaly, the service/repo should ensure it cannot happen
|
||||
|
||||
@@ -1034,7 +1034,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
var cannotBePublished = publishedCultures.Count == 0; // no published cultures = cannot be published
|
||||
if (!cannotBePublished)
|
||||
{
|
||||
var mandatoryCultures = _languageRepository.GetMany().Where(x => x.Mandatory).Select(x => x.IsoCode);
|
||||
var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode);
|
||||
cannotBePublished = mandatoryCultures.Any(x => !publishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); // missing mandatory culture = cannot be published
|
||||
}
|
||||
|
||||
|
||||
@@ -363,6 +363,16 @@ namespace Umbraco.Core.Services.Implement
|
||||
// write-lock languages to guard against race conds when dealing with default language
|
||||
scope.WriteLock(Constants.Locks.Languages);
|
||||
|
||||
// look for cycles - within write-lock
|
||||
if (language.FallbackLanguageId.HasValue)
|
||||
{
|
||||
var languages = _languageRepository.GetMany().ToDictionary(x => x.Id, x => x);
|
||||
if (!languages.ContainsKey(language.FallbackLanguageId.Value))
|
||||
throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback id={language.FallbackLanguageId.Value} which is not a valid language id.");
|
||||
if (CreatesCycle(language, languages))
|
||||
throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback {languages[language.FallbackLanguageId.Value].IsoCode} as it would create a fallback cycle.");
|
||||
}
|
||||
|
||||
var saveEventArgs = new SaveEventArgs<ILanguage>(language);
|
||||
if (scope.Events.DispatchCancelable(SavingLanguage, this, saveEventArgs))
|
||||
{
|
||||
@@ -380,6 +390,20 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreatesCycle(ILanguage language, IDictionary<int, ILanguage> languages)
|
||||
{
|
||||
// a new language is not referenced yet, so cannot be part of a cycle
|
||||
if (!language.HasIdentity) return false;
|
||||
|
||||
var id = language.FallbackLanguageId;
|
||||
while (true) // assuming languages does not already contains a cycle, this must end
|
||||
{
|
||||
if (!id.HasValue) return false; // no fallback means no cycle
|
||||
if (id.Value == language.Id) return true; // back to language = cycle!
|
||||
id = languages[id.Value].FallbackLanguageId; // else keep chaining
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <see cref="ILanguage"/> by removing it (but not its usages) from the db
|
||||
/// </summary>
|
||||
@@ -399,8 +423,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
return;
|
||||
}
|
||||
|
||||
//NOTE: There isn't any constraints in the db, so possible references aren't deleted
|
||||
|
||||
// NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted
|
||||
_languageRepository.Delete(language);
|
||||
deleteEventArgs.CanCancel = false;
|
||||
|
||||
|
||||
@@ -362,6 +362,7 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorVariantsModel.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\SuperZero.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\TagsMigration.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\FallbackLanguage.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\UpdateDefaultMandatoryLanguage.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\UserForeignKeys.cs" />
|
||||
<Compile Include="Models\AuditEntry.cs" />
|
||||
@@ -391,12 +392,14 @@
|
||||
<Compile Include="Models\PathValidationExtensions.cs" />
|
||||
<Compile Include="Models\Entities\TreeEntityBase.cs" />
|
||||
<Compile Include="Models\PropertyTagsExtensions.cs" />
|
||||
<Compile Include="Models\PublishedContent\Fallback.cs" />
|
||||
<Compile Include="Models\PublishedContent\NoopPublishedValueFallback.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedCultureInfos.cs" />
|
||||
<Compile Include="Models\PublishedContent\IVariationContextAccessor.cs" />
|
||||
<Compile Include="Models\PublishedContent\IPublishedValueFallback.cs" />
|
||||
<Compile Include="Models\PublishedContent\VariationContext.cs" />
|
||||
<Compile Include="Models\PublishedContent\ThreadCultureVariationContextAccessor.cs" />
|
||||
<Compile Include="Models\PublishedContent\VariationContextAccessorExtensions.cs" />
|
||||
<Compile Include="Persistence\Dtos\AuditEntryDto.cs" />
|
||||
<Compile Include="Persistence\Dtos\ConsentDto.cs" />
|
||||
<Compile Include="Persistence\Dtos\ContentVersionCultureVariationDto.cs" />
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Umbraco.Tests.Composing
|
||||
Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree]
|
||||
|
||||
typesFound = TypeFinder.FindClassesWithAttribute<TreeAttribute>(new[] { typeof (UmbracoContext).Assembly });
|
||||
Assert.AreEqual(21, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree]
|
||||
Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree]
|
||||
}
|
||||
|
||||
private static ProfilingLogger GetTestProfilingLogger()
|
||||
|
||||
@@ -1,114 +1,114 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<dashBoard>
|
||||
|
||||
<section alias="StartupSettingsDashboardSection">
|
||||
<areas>
|
||||
<area>settings</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="hello">
|
||||
views/dashboard/settings/settingsdashboardintro.html
|
||||
</control>
|
||||
<control showOnce="false" addPanel="false" panelCaption="">
|
||||
views/dashboard/settings/settingsdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupSettingsDashboardSection">
|
||||
<areas>
|
||||
<area>settings</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="hello">
|
||||
views/dashboard/settings/settingsdashboardintro.html
|
||||
</control>
|
||||
<control showOnce="false" addPanel="false" panelCaption="">
|
||||
views/dashboard/settings/settingsdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Examine Management">
|
||||
<control>dashboard/ExamineManagement.ascx</control>
|
||||
</tab>
|
||||
</section>
|
||||
|
||||
<section alias="StartupDeveloperDashboardSection">
|
||||
<areas>
|
||||
<area>developer</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/developer/developerdashboardintro.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/developer/developerdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Examine Management">
|
||||
<control>dashboard/ExamineManagement.ascx</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupDeveloperDashboardSection">
|
||||
<areas>
|
||||
<area>developer</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/developer/developerdashboardintro.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/developer/developerdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
|
||||
<section alias="StartupMediaDashboardSection">
|
||||
<areas>
|
||||
<area>media</area>
|
||||
</areas>
|
||||
<tab caption="Content">
|
||||
<control showOnce="false" addPanel="false" panelCaption="">
|
||||
views/dashboard/media/mediafolderbrowser.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Get Started">
|
||||
<access>
|
||||
<grant>admin</grant>
|
||||
</access>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/media/mediadashboardintro.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/media/desktopmediauploader.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/media/mediadashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupMediaDashboardSection">
|
||||
<areas>
|
||||
<area>media</area>
|
||||
</areas>
|
||||
<tab caption="Content">
|
||||
<control showOnce="false" addPanel="false" panelCaption="">
|
||||
views/dashboard/media/mediafolderbrowser.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Get Started">
|
||||
<access>
|
||||
<grant>admin</grant>
|
||||
</access>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/media/mediadashboardintro.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/media/desktopmediauploader.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/media/mediadashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
|
||||
<section alias="StartupDashboardSection">
|
||||
<access>
|
||||
<deny>translator</deny>
|
||||
<grant>hello</grant>
|
||||
<grantBySection>world</grantBySection>
|
||||
</access>
|
||||
<areas>
|
||||
<area>content</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<access>
|
||||
<grant>admin</grant>
|
||||
</access>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/default/startupdashboardintro.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/default/startupdashboardkits.html
|
||||
<section alias="StartupDashboardSection">
|
||||
<access>
|
||||
<deny>editor</deny>
|
||||
<deny>writer</deny>
|
||||
<deny>translator</deny>
|
||||
<grant>hello</grant>
|
||||
<grantBySection>world</grantBySection>
|
||||
</access>
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/default/startupdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Last Edits">
|
||||
<control addPanel="true" MaxRecords="30">dashboard/latestEdits.ascx</control>
|
||||
</tab>
|
||||
<tab caption="Change Password">
|
||||
<control addPanel="true">
|
||||
views/dashboard/changepassword.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<areas>
|
||||
<area>content</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<access>
|
||||
<grant>admin</grant>
|
||||
</access>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/default/startupdashboardintro.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/default/startupdashboardkits.html
|
||||
<access>
|
||||
<deny>editor</deny>
|
||||
<deny>writer</deny>
|
||||
</access>
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/default/startupdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Last Edits">
|
||||
<control addPanel="true" MaxRecords="30">dashboard/latestEdits.ascx</control>
|
||||
</tab>
|
||||
<tab caption="Change Password">
|
||||
<control addPanel="true">
|
||||
views/dashboard/changepassword.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
|
||||
<section alias="StartupMemberDashboardSection">
|
||||
<areas>
|
||||
<area>default</area>
|
||||
<area>member</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/members/membersdashboardintro.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
members/membersearch.ascx
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/members/membersdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupMemberDashboardSection">
|
||||
<areas>
|
||||
<area>default</area>
|
||||
<area>member</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/members/membersdashboardintro.html
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
members/membersearch.ascx
|
||||
</control>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/members/membersdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
</dashBoard>
|
||||
|
||||
@@ -66,10 +66,21 @@ namespace Umbraco.Tests.Configurations.DashboardSettings
|
||||
[Test]
|
||||
public void Test_Section_Tabs()
|
||||
{
|
||||
Assert.AreEqual(1, SettingsSection.Sections.ElementAt(0).Tabs.Count());
|
||||
Assert.AreEqual(2, SettingsSection.Sections.ElementAt(1).Tabs.Count());
|
||||
//Element 0 Alias "StartupSettingsDashboardSection"
|
||||
Assert.AreEqual(2, SettingsSection.Sections.ElementAt(0).Tabs.Count());
|
||||
|
||||
//Element 1 Alias "StartupDeveloperDashboardSection"
|
||||
Assert.AreEqual(1, SettingsSection.Sections.ElementAt(1).Tabs.Count());
|
||||
|
||||
//Element 2 Alias "StartupMediaDashboardSection"
|
||||
Assert.AreEqual(2, SettingsSection.Sections.ElementAt(2).Tabs.Count());
|
||||
|
||||
//Element 3 Alias "StartupDashboardSection"
|
||||
Assert.AreEqual(3, SettingsSection.Sections.ElementAt(3).Tabs.Count());
|
||||
|
||||
//Element 4 Alias "StartupMemberDashboardSection"
|
||||
Assert.AreEqual(1, SettingsSection.Sections.ElementAt(4).Tabs.Count());
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(language.HasIdentity, Is.True);
|
||||
Assert.That(language.CultureName, Is.EqualTo("English (United States)"));
|
||||
Assert.That(language.IsoCode, Is.EqualTo("en-US"));
|
||||
Assert.That(language.FallbackLanguageId, Is.Null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +62,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var au = CultureInfo.GetCultureInfo("en-AU");
|
||||
var language = (ILanguage)new Language(au.Name)
|
||||
{
|
||||
CultureName = au.DisplayName
|
||||
CultureName = au.DisplayName,
|
||||
FallbackLanguageId = 1
|
||||
};
|
||||
repository.Save(language);
|
||||
|
||||
@@ -73,6 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(language.HasIdentity, Is.True);
|
||||
Assert.That(language.CultureName, Is.EqualTo(au.DisplayName));
|
||||
Assert.That(language.IsoCode, Is.EqualTo(au.Name));
|
||||
Assert.That(language.FallbackLanguageId, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,14 +185,15 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
// Act
|
||||
var languageBR = new Language("pt-BR") {CultureName = "pt-BR"};
|
||||
var languageBR = new Language("pt-BR") { CultureName = "pt-BR" };
|
||||
repository.Save(languageBR);
|
||||
|
||||
// Assert
|
||||
Assert.That(languageBR.HasIdentity, Is.True);
|
||||
Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6
|
||||
Assert.IsFalse(languageBR.IsDefaultVariantLanguage);
|
||||
Assert.IsFalse(languageBR.Mandatory);
|
||||
Assert.IsFalse(languageBR.IsDefault);
|
||||
Assert.IsFalse(languageBR.IsMandatory);
|
||||
Assert.IsNull(languageBR.FallbackLanguageId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,14 +207,39 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
// Act
|
||||
var languageBR = new Language("pt-BR") { CultureName = "pt-BR", IsDefaultVariantLanguage = true, Mandatory = true };
|
||||
var languageBR = new Language("pt-BR") { CultureName = "pt-BR", IsDefault = true, IsMandatory = true };
|
||||
repository.Save(languageBR);
|
||||
|
||||
// Assert
|
||||
Assert.That(languageBR.HasIdentity, Is.True);
|
||||
Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6
|
||||
Assert.IsTrue(languageBR.IsDefaultVariantLanguage);
|
||||
Assert.IsTrue(languageBR.Mandatory);
|
||||
Assert.IsTrue(languageBR.IsDefault);
|
||||
Assert.IsTrue(languageBR.IsMandatory);
|
||||
Assert.IsNull(languageBR.FallbackLanguageId);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Add_On_LanguageRepository_With_Fallback_Language()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
// Act
|
||||
var languageBR = new Language("pt-BR")
|
||||
{
|
||||
CultureName = "pt-BR",
|
||||
FallbackLanguageId = 1
|
||||
};
|
||||
repository.Save(languageBR);
|
||||
|
||||
// Assert
|
||||
Assert.That(languageBR.HasIdentity, Is.True);
|
||||
Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6
|
||||
Assert.That(languageBR.FallbackLanguageId, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,24 +252,22 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var languageBR = (ILanguage)new Language("pt-BR") { CultureName = "pt-BR", IsDefaultVariantLanguage = true, Mandatory = true };
|
||||
var languageBR = (ILanguage)new Language("pt-BR") { CultureName = "pt-BR", IsDefault = true, IsMandatory = true };
|
||||
repository.Save(languageBR);
|
||||
var languageEN = new Language("en-AU") { CultureName = "en-AU" };
|
||||
repository.Save(languageEN);
|
||||
|
||||
Assert.IsTrue(languageBR.IsDefaultVariantLanguage);
|
||||
Assert.IsTrue(languageBR.Mandatory);
|
||||
Assert.IsTrue(languageBR.IsDefault);
|
||||
Assert.IsTrue(languageBR.IsMandatory);
|
||||
|
||||
// Act
|
||||
|
||||
var languageNZ = new Language("en-NZ") { CultureName = "en-NZ", IsDefaultVariantLanguage = true, Mandatory = true };
|
||||
var languageNZ = new Language("en-NZ") { CultureName = "en-NZ", IsDefault = true, IsMandatory = true };
|
||||
repository.Save(languageNZ);
|
||||
languageBR = repository.Get(languageBR.Id);
|
||||
|
||||
// Assert
|
||||
|
||||
Assert.IsFalse(languageBR.IsDefaultVariantLanguage);
|
||||
Assert.IsTrue(languageNZ.IsDefaultVariantLanguage);
|
||||
Assert.IsFalse(languageBR.IsDefault);
|
||||
Assert.IsTrue(languageNZ.IsDefault);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +284,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var language = repository.Get(5);
|
||||
language.IsoCode = "pt-BR";
|
||||
language.CultureName = "pt-BR";
|
||||
language.FallbackLanguageId = 1;
|
||||
|
||||
repository.Save(language);
|
||||
|
||||
@@ -266,6 +294,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(languageUpdated, Is.Not.Null);
|
||||
Assert.That(languageUpdated.IsoCode, Is.EqualTo("pt-BR"));
|
||||
Assert.That(languageUpdated.CultureName, Is.EqualTo("pt-BR"));
|
||||
Assert.That(languageUpdated.FallbackLanguageId, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +318,30 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Delete_On_LanguageRepository_With_Language_Used_As_Fallback()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
// Add language to delete as a fall-back language to another one
|
||||
var repository = CreateRepository(provider);
|
||||
var languageToFallbackFrom = repository.Get(5);
|
||||
languageToFallbackFrom.FallbackLanguageId = 2; // fall back to #2 (something we can delete)
|
||||
repository.Save(languageToFallbackFrom);
|
||||
|
||||
// delete #2
|
||||
var languageToDelete = repository.Get(2);
|
||||
repository.Delete(languageToDelete);
|
||||
|
||||
var exists = repository.Exists(2);
|
||||
|
||||
// has been deleted
|
||||
Assert.That(exists, Is.False);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Exists_On_LanguageRepository()
|
||||
{
|
||||
@@ -314,8 +367,10 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
base.TearDown();
|
||||
}
|
||||
|
||||
public void CreateTestData()
|
||||
private void CreateTestData()
|
||||
{
|
||||
//Id 1 is en-US - when Umbraco is installed
|
||||
|
||||
var languageDK = new Language("da-DK") { CultureName = "da-DK" };
|
||||
ServiceContext.LocalizationService.Save(languageDK);//Id 2
|
||||
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(PluginManager = UmbracoTestOptions.PluginManager.PerFixture)]
|
||||
public class PublishedContentLanguageVariantTests : PublishedContentSnapshotTestBase
|
||||
{
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
Container.RegisterSingleton(_ => GetServiceContext());
|
||||
}
|
||||
|
||||
protected ServiceContext GetServiceContext()
|
||||
{
|
||||
var serviceContext = TestObjects.GetServiceContextMock(Container);
|
||||
MockLocalizationService(serviceContext);
|
||||
return serviceContext;
|
||||
}
|
||||
|
||||
private static void MockLocalizationService(ServiceContext serviceContext)
|
||||
{
|
||||
// Set up languages.
|
||||
// Spanish falls back to English and Italian to Spanish (and then to English).
|
||||
// French has no fall back.
|
||||
// Danish, Swedish and Norweigan create an invalid loop.
|
||||
var languages = new List<Language>
|
||||
{
|
||||
new Language("en-US") { Id = 1, CultureName = "English", IsDefault = true },
|
||||
new Language("fr") { Id = 2, CultureName = "French" },
|
||||
new Language("es") { Id = 3, CultureName = "Spanish", FallbackLanguageId = 1 },
|
||||
new Language("it") { Id = 4, CultureName = "Italian", FallbackLanguageId = 3 },
|
||||
new Language("de") { Id = 5, CultureName = "German" },
|
||||
new Language("da") { Id = 6, CultureName = "Danish", FallbackLanguageId = 8 },
|
||||
new Language("sv") { Id = 7, CultureName = "Swedish", FallbackLanguageId = 6 },
|
||||
new Language("no") { Id = 8, CultureName = "Norweigan", FallbackLanguageId = 7 },
|
||||
new Language("nl") { Id = 9, CultureName = "Dutch", FallbackLanguageId = 1 }
|
||||
};
|
||||
|
||||
var localizationService = Mock.Get(serviceContext.LocalizationService);
|
||||
localizationService.Setup(x => x.GetAllLanguages()).Returns(languages);
|
||||
localizationService.Setup(x => x.GetLanguageById(It.IsAny<int>()))
|
||||
.Returns((int id) => languages.SingleOrDefault(y => y.Id == id));
|
||||
localizationService.Setup(x => x.GetLanguageByIsoCode(It.IsAny<string>()))
|
||||
.Returns((string c) => languages.SingleOrDefault(y => y.IsoCode == c));
|
||||
}
|
||||
|
||||
internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache)
|
||||
{
|
||||
var props = new[]
|
||||
{
|
||||
factory.CreatePropertyType("prop1", 1),
|
||||
factory.CreatePropertyType("welcomeText", 1),
|
||||
factory.CreatePropertyType("welcomeText2", 1),
|
||||
};
|
||||
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), props);
|
||||
|
||||
var prop1 = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "welcomeText",
|
||||
};
|
||||
prop1.SetSourceValue("en-US", "Welcome", true);
|
||||
prop1.SetValue("en-US", "Welcome", true);
|
||||
prop1.SetSourceValue("de", "Willkommen");
|
||||
prop1.SetValue("de", "Willkommen");
|
||||
prop1.SetSourceValue("nl", "Welkom");
|
||||
prop1.SetValue("nl", "Welkom");
|
||||
|
||||
var prop2 = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "welcomeText2",
|
||||
};
|
||||
prop2.SetSourceValue("en-US", "Welcome", true);
|
||||
prop2.SetValue("en-US", "Welcome", true);
|
||||
|
||||
var prop3 = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "welcomeText",
|
||||
};
|
||||
prop3.SetSourceValue("en-US", "Welcome", true);
|
||||
prop3.SetValue("en-US", "Welcome", true);
|
||||
|
||||
var item1 = new SolidPublishedContent(contentType1)
|
||||
{
|
||||
Id = 1,
|
||||
SortOrder = 0,
|
||||
Name = "Content 1",
|
||||
UrlSegment = "content-1",
|
||||
Path = "/1",
|
||||
Level = 1,
|
||||
Url = "/content-1",
|
||||
ParentId = -1,
|
||||
ChildIds = new[] { 2 },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
prop1, prop2
|
||||
}
|
||||
};
|
||||
|
||||
var item2 = new SolidPublishedContent(contentType1)
|
||||
{
|
||||
Id = 2,
|
||||
SortOrder = 0,
|
||||
Name = "Content 2",
|
||||
UrlSegment = "content-2",
|
||||
Path = "/1/2",
|
||||
Level = 2,
|
||||
Url = "/content-1/content-2",
|
||||
ParentId = 1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
prop3
|
||||
}
|
||||
};
|
||||
|
||||
item1.Children = new List<IPublishedContent> { item2 };
|
||||
item2.Parent = item1;
|
||||
|
||||
cache.Add(item1);
|
||||
cache.Add(item2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Populated_Requested_Language()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "en-US");
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Populated_Requested_Non_Default_Language()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "de");
|
||||
Assert.AreEqual("Willkommen", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "fr");
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Unless_Requested()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "es");
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "es", fallback: Fallback.ToLanguage);
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors));
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "no", fallback: Fallback.ToLanguage);
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_Get_Content_Recursively_Unless_Requested()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
|
||||
var value = content.Value("welcomeText2");
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_Recursively()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
|
||||
var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors);
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_With_Recursive_Priority()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
|
||||
var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language));
|
||||
|
||||
// No Dutch value is directly assigned. Check has fallen back to Dutch value from parent.
|
||||
Assert.AreEqual("Welkom", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_With_Fallback_Language_Priority()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
|
||||
var value = content.Value("welcomeText", "nl", fallback: Fallback.ToLanguage);
|
||||
|
||||
// No Dutch value is directly assigned. Check has fallen back to English value from language variant.
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Throws_For_Non_Supported_Fallback()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
|
||||
Assert.Throws<NotSupportedException>(() => content.Value("welcomeText", "nl", fallback: Fallback.To(999)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Fallback_To_Default_Value()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
|
||||
|
||||
// no Dutch value is assigned, so getting null
|
||||
var value = content.Value("welcomeText", "nl");
|
||||
Assert.IsNull(value);
|
||||
|
||||
// even if we 'just' provide a default value
|
||||
value = content.Value("welcomeText", "nl", defaultValue: "woop");
|
||||
Assert.IsNull(value);
|
||||
|
||||
// but it works with proper fallback settings
|
||||
value = content.Value("welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop");
|
||||
Assert.AreEqual("woop", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Have_Custom_Default_Value()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
|
||||
|
||||
// hack the value, pretend the converter would return something
|
||||
var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants;
|
||||
Assert.IsNotNull(prop);
|
||||
prop.SetValue("nl", "nope"); // HasValue false but getting value returns this
|
||||
|
||||
// there is an EN value
|
||||
var value = content.Value("welcomeText", "en-US");
|
||||
Assert.AreEqual("Welcome", value);
|
||||
|
||||
// there is no NL value and we get the 'converted' value
|
||||
value = content.Value("welcomeText", "nl");
|
||||
Assert.AreEqual("nope", value);
|
||||
|
||||
// but it works with proper fallback settings
|
||||
value = content.Value("welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop");
|
||||
Assert.AreEqual("woop", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Web.Routing;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Core.Composing;
|
||||
using Current = Umbraco.Core.Composing.Current;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(PluginManager = UmbracoTestOptions.PluginManager.PerFixture)]
|
||||
public class PublishedContentMoreTests : PublishedContentTestBase
|
||||
public class PublishedContentMoreTests : PublishedContentSnapshotTestBase
|
||||
{
|
||||
// read http://stackoverflow.com/questions/7713326/extension-method-that-works-on-ienumerablet-and-iqueryablet
|
||||
// and http://msmvps.com/blogs/jon_skeet/archive/2010/10/28/overloading-and-generic-constraints.aspx
|
||||
// and http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx
|
||||
|
||||
public override void SetUp()
|
||||
internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache)
|
||||
{
|
||||
base.SetUp();
|
||||
var props = new[]
|
||||
{
|
||||
factory.CreatePropertyType("prop1", 1),
|
||||
};
|
||||
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), props);
|
||||
var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty<string>(), props);
|
||||
var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty<string>(), props);
|
||||
|
||||
var umbracoContext = GetUmbracoContext();
|
||||
Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext;
|
||||
}
|
||||
cache.Add(new SolidPublishedContent(contentType1)
|
||||
{
|
||||
Id = 1,
|
||||
SortOrder = 0,
|
||||
Name = "Content 1",
|
||||
UrlSegment = "content-1",
|
||||
Path = "/1",
|
||||
Level = 1,
|
||||
Url = "/content-1",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
cache.Add(new SolidPublishedContent(contentType2)
|
||||
{
|
||||
Id = 2,
|
||||
SortOrder = 1,
|
||||
Name = "Content 2",
|
||||
UrlSegment = "content-2",
|
||||
Path = "/2",
|
||||
Level = 1,
|
||||
Url = "/content-2",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Container.RegisterSingleton<IPublishedModelFactory>(f => new PublishedModelFactory(f.GetInstance<TypeLoader>().GetTypes<PublishedContentModel>()));
|
||||
}
|
||||
|
||||
protected override TypeLoader CreatePluginManager(IServiceFactory f)
|
||||
{
|
||||
var pluginManager = base.CreatePluginManager(f);
|
||||
|
||||
// this is so the model factory looks into the test assembly
|
||||
pluginManager.AssembliesToScan = pluginManager.AssembliesToScan
|
||||
.Union(new[] { typeof (PublishedContentMoreTests).Assembly })
|
||||
.ToList();
|
||||
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
private UmbracoContext GetUmbracoContext()
|
||||
{
|
||||
RouteData routeData = null;
|
||||
|
||||
var publishedSnapshot = CreatePublishedSnapshot();
|
||||
|
||||
var publishedSnapshotService = new Mock<IPublishedSnapshotService>();
|
||||
publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(publishedSnapshot);
|
||||
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
|
||||
var httpContext = GetHttpContextFactory("http://umbraco.local/", routeData).HttpContext;
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContext,
|
||||
publishedSnapshotService.Object,
|
||||
new WebSecurity(httpContext, Current.Services.UserService, globalSettings),
|
||||
TestObjects.GetUmbracoSettings(),
|
||||
Enumerable.Empty<IUrlProvider>(),
|
||||
globalSettings,
|
||||
new TestVariationContextAccessor());
|
||||
|
||||
return umbracoContext;
|
||||
}
|
||||
|
||||
public override void TearDown()
|
||||
{
|
||||
base.TearDown();
|
||||
|
||||
Current.Reset();
|
||||
cache.Add(new SolidPublishedContent(contentType2Sub)
|
||||
{
|
||||
Id = 3,
|
||||
SortOrder = 2,
|
||||
Name = "Content 2Sub",
|
||||
UrlSegment = "content-2sub",
|
||||
Path = "/3",
|
||||
Level = 1,
|
||||
Url = "/content-2sub",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -197,95 +200,5 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.AreEqual(1, result[0].Id);
|
||||
Assert.AreEqual(2, result[1].Id);
|
||||
}
|
||||
|
||||
private static SolidPublishedSnapshot CreatePublishedSnapshot()
|
||||
{
|
||||
var dataTypeService = new TestObjects.TestDataTypeService(
|
||||
new DataType(new VoidEditor(Mock.Of<ILogger>())) { Id = 1 });
|
||||
|
||||
var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);
|
||||
var caches = new SolidPublishedSnapshot();
|
||||
var cache = caches.InnerContentCache;
|
||||
|
||||
var props = new[]
|
||||
{
|
||||
factory.CreatePropertyType("prop1", 1),
|
||||
};
|
||||
|
||||
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), props);
|
||||
var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty<string>(), props);
|
||||
var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty<string>(), props);
|
||||
|
||||
cache.Add(new SolidPublishedContent(contentType1)
|
||||
{
|
||||
Id = 1,
|
||||
SortOrder = 0,
|
||||
Name = "Content 1",
|
||||
UrlSegment = "content-1",
|
||||
Path = "/1",
|
||||
Level = 1,
|
||||
Url = "/content-1",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] {},
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cache.Add(new SolidPublishedContent(contentType2)
|
||||
{
|
||||
Id = 2,
|
||||
SortOrder = 1,
|
||||
Name = "Content 2",
|
||||
UrlSegment = "content-2",
|
||||
Path = "/2",
|
||||
Level = 1,
|
||||
Url = "/content-2",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cache.Add(new SolidPublishedContent(contentType2Sub)
|
||||
{
|
||||
Id = 3,
|
||||
SortOrder = 2,
|
||||
Name = "Content 2Sub",
|
||||
UrlSegment = "content-2sub",
|
||||
Path = "/3",
|
||||
Level = 1,
|
||||
Url = "/content-2sub",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return caches;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Web.Routing;
|
||||
using Moq;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Core.Composing;
|
||||
using Current = Umbraco.Core.Composing.Current;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
public abstract class PublishedContentSnapshotTestBase : PublishedContentTestBase
|
||||
{
|
||||
// read http://stackoverflow.com/questions/7713326/extension-method-that-works-on-ienumerablet-and-iqueryablet
|
||||
// and http://msmvps.com/blogs/jon_skeet/archive/2010/10/28/overloading-and-generic-constraints.aspx
|
||||
// and http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
var umbracoContext = GetUmbracoContext();
|
||||
Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
Container.RegisterSingleton<IPublishedModelFactory>(f => new PublishedModelFactory(f.GetInstance<TypeLoader>().GetTypes<PublishedContentModel>()));
|
||||
}
|
||||
|
||||
protected override TypeLoader CreatePluginManager(IServiceFactory f)
|
||||
{
|
||||
var pluginManager = base.CreatePluginManager(f);
|
||||
|
||||
// this is so the model factory looks into the test assembly
|
||||
pluginManager.AssembliesToScan = pluginManager.AssembliesToScan
|
||||
.Union(new[] { typeof (PublishedContentMoreTests).Assembly })
|
||||
.ToList();
|
||||
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
private UmbracoContext GetUmbracoContext()
|
||||
{
|
||||
RouteData routeData = null;
|
||||
|
||||
var publishedSnapshot = CreatePublishedSnapshot();
|
||||
|
||||
var publishedSnapshotService = new Mock<IPublishedSnapshotService>();
|
||||
publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(publishedSnapshot);
|
||||
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
|
||||
var httpContext = GetHttpContextFactory("http://umbraco.local/", routeData).HttpContext;
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContext,
|
||||
publishedSnapshotService.Object,
|
||||
new WebSecurity(httpContext, Current.Services.UserService, globalSettings),
|
||||
TestObjects.GetUmbracoSettings(),
|
||||
Enumerable.Empty<IUrlProvider>(),
|
||||
globalSettings,
|
||||
new TestVariationContextAccessor());
|
||||
|
||||
return umbracoContext;
|
||||
}
|
||||
|
||||
public override void TearDown()
|
||||
{
|
||||
base.TearDown();
|
||||
|
||||
Current.Reset();
|
||||
}
|
||||
|
||||
private SolidPublishedSnapshot CreatePublishedSnapshot()
|
||||
{
|
||||
var dataTypeService = new TestObjects.TestDataTypeService(
|
||||
new DataType(new VoidEditor(Mock.Of<ILogger>())) { Id = 1 });
|
||||
|
||||
var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);
|
||||
var caches = new SolidPublishedSnapshot();
|
||||
var cache = caches.InnerContentCache;
|
||||
PopulateCache(factory, cache);
|
||||
return caches;
|
||||
}
|
||||
|
||||
internal abstract void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var dataTypeService = new TestObjects.TestDataTypeService(
|
||||
new DataType(new VoidEditor(logger)) { Id = 1},
|
||||
new DataType(new VoidEditor(logger)) { Id = 1 },
|
||||
new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 },
|
||||
new DataType(new RichTextPropertyEditor(logger)) { Id = 1002 },
|
||||
new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 },
|
||||
@@ -338,11 +338,11 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPropertyValueRecursiveTest()
|
||||
public void Get_Property_Value_Recursive()
|
||||
{
|
||||
var doc = GetNode(1174);
|
||||
var rVal = doc.Value("testRecursive", recurse: true);
|
||||
var nullVal = doc.Value("DoNotFindThis", recurse: true);
|
||||
var rVal = doc.Value("testRecursive", fallback: Fallback.ToAncestors);
|
||||
var nullVal = doc.Value("DoNotFindThis", fallback: Fallback.ToAncestors);
|
||||
Assert.AreEqual("This is the recursive val", rVal);
|
||||
Assert.AreEqual(null, nullVal);
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
#endregion
|
||||
}
|
||||
|
||||
class SolidPublishedProperty : IPublishedProperty
|
||||
internal class SolidPublishedProperty : IPublishedProperty
|
||||
{
|
||||
public PublishedPropertyType PropertyType { get; set; }
|
||||
public string Alias { get; set; }
|
||||
@@ -257,10 +257,86 @@ namespace Umbraco.Tests.PublishedContent
|
||||
public bool SolidHasValue { get; set; }
|
||||
public object SolidXPathValue { get; set; }
|
||||
|
||||
public object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue;
|
||||
public object GetValue(string culture = null, string segment = null) => SolidValue;
|
||||
public object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue;
|
||||
public bool HasValue(string culture = null, string segment = null) => SolidHasValue;
|
||||
public virtual object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue;
|
||||
public virtual object GetValue(string culture = null, string segment = null) => SolidValue;
|
||||
public virtual object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue;
|
||||
public virtual bool HasValue(string culture = null, string segment = null) => SolidHasValue;
|
||||
}
|
||||
|
||||
internal class SolidPublishedPropertyWithLanguageVariants : SolidPublishedProperty
|
||||
{
|
||||
private readonly IDictionary<string, object> _solidSourceValues = new Dictionary<string, object>();
|
||||
private readonly IDictionary<string, object> _solidValues = new Dictionary<string, object>();
|
||||
private readonly IDictionary<string, object> _solidXPathValues = new Dictionary<string, object>();
|
||||
|
||||
public override object GetSourceValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetSourceValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidSourceValues.ContainsKey(culture) ? _solidSourceValues[culture] : null;
|
||||
}
|
||||
|
||||
public override object GetValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidValues.ContainsKey(culture) ? _solidValues[culture] : null;
|
||||
}
|
||||
|
||||
public override object GetXPathValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetXPathValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidXPathValues.ContainsKey(culture) ? _solidXPathValues[culture] : null;
|
||||
}
|
||||
|
||||
public override bool HasValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.HasValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidSourceValues.ContainsKey(culture);
|
||||
}
|
||||
|
||||
public void SetSourceValue(string culture, object value, bool defaultValue = false)
|
||||
{
|
||||
_solidSourceValues.Add(culture, value);
|
||||
if (defaultValue)
|
||||
{
|
||||
SolidSourceValue = value;
|
||||
SolidHasValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValue(string culture, object value, bool defaultValue = false)
|
||||
{
|
||||
_solidValues.Add(culture, value);
|
||||
if (defaultValue)
|
||||
{
|
||||
SolidValue = value;
|
||||
SolidHasValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetXPathValue(string culture, object value, bool defaultValue = false)
|
||||
{
|
||||
_solidXPathValues.Add(culture, value);
|
||||
if (defaultValue)
|
||||
{
|
||||
SolidXPathValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[PublishedModel("ContentType2")]
|
||||
|
||||
@@ -2489,7 +2489,7 @@ namespace Umbraco.Tests.Services
|
||||
{
|
||||
var languageService = ServiceContext.LocalizationService;
|
||||
|
||||
var langUk = new Language("en-UK") { IsDefaultVariantLanguage = true };
|
||||
var langUk = new Language("en-UK") { IsDefault = true };
|
||||
var langFr = new Language("fr-FR");
|
||||
|
||||
languageService.Save(langFr);
|
||||
@@ -2524,7 +2524,7 @@ namespace Umbraco.Tests.Services
|
||||
{
|
||||
var languageService = ServiceContext.LocalizationService;
|
||||
|
||||
var langUk = new Language("en-UK") { IsDefaultVariantLanguage = true };
|
||||
var langUk = new Language("en-UK") { IsDefault = true };
|
||||
var langFr = new Language("fr-FR");
|
||||
|
||||
languageService.Save(langFr);
|
||||
@@ -2562,7 +2562,7 @@ namespace Umbraco.Tests.Services
|
||||
var languageService = ServiceContext.LocalizationService;
|
||||
|
||||
//var langFr = new Language("fr-FR") { IsDefaultVariantLanguage = true };
|
||||
var langXx = new Language("pt-PT") { IsDefaultVariantLanguage = true };
|
||||
var langXx = new Language("pt-PT") { IsDefault = true };
|
||||
var langFr = new Language("fr-FR");
|
||||
var langUk = new Language("en-UK");
|
||||
var langDe = new Language("de-DE");
|
||||
|
||||
@@ -192,6 +192,20 @@ namespace Umbraco.Tests.Services
|
||||
Assert.Null(language);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Delete_Language_Used_As_Fallback()
|
||||
{
|
||||
var danish = ServiceContext.LocalizationService.GetLanguageByIsoCode("da-DK");
|
||||
var norwegian = new Language("nb-NO") { CultureName = "Norwegian", FallbackLanguageId = danish.Id };
|
||||
ServiceContext.LocalizationService.Save(norwegian, 0);
|
||||
var languageId = danish.Id;
|
||||
|
||||
ServiceContext.LocalizationService.Delete(danish);
|
||||
|
||||
var language = ServiceContext.LocalizationService.GetLanguageById(languageId);
|
||||
Assert.Null(language);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Create_DictionaryItem_At_Root()
|
||||
{
|
||||
@@ -362,21 +376,21 @@ namespace Umbraco.Tests.Services
|
||||
{
|
||||
var localizationService = ServiceContext.LocalizationService;
|
||||
var language = new Core.Models.Language("en-AU");
|
||||
language.IsDefaultVariantLanguage = true;
|
||||
language.IsDefault = true;
|
||||
localizationService.Save(language);
|
||||
var result = localizationService.GetLanguageById(language.Id);
|
||||
|
||||
Assert.IsTrue(result.IsDefaultVariantLanguage);
|
||||
Assert.IsTrue(result.IsDefault);
|
||||
|
||||
var language2 = new Core.Models.Language("en-NZ");
|
||||
language2.IsDefaultVariantLanguage = true;
|
||||
language2.IsDefault = true;
|
||||
localizationService.Save(language2);
|
||||
var result2 = localizationService.GetLanguageById(language2.Id);
|
||||
//re-get
|
||||
result = localizationService.GetLanguageById(language.Id);
|
||||
|
||||
Assert.IsTrue(result2.IsDefaultVariantLanguage);
|
||||
Assert.IsFalse(result.IsDefaultVariantLanguage);
|
||||
Assert.IsTrue(result2.IsDefault);
|
||||
Assert.IsFalse(result.IsDefault);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
<Compile Include="Migrations\MigrationTests.cs" />
|
||||
<Compile Include="Models\PathValidationTests.cs" />
|
||||
<Compile Include="Models\VariationTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
|
||||
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
|
||||
<Compile Include="PublishedContent\NuCacheTests.cs" />
|
||||
<Compile Include="Testing\Objects\TestDataSource.cs" />
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"angular": "~1.7.2",
|
||||
"angular-cookies": "~1.7.2",
|
||||
"angular-sanitize": "~1.7.2",
|
||||
"angular-touch": "~1.7.2",
|
||||
"angular-route": "~1.7.2",
|
||||
"angular-animate": "~1.7.2",
|
||||
"angular-i18n": "~1.7.2",
|
||||
"angular": "~1.7.4",
|
||||
"angular-cookies": "~1.7.4",
|
||||
"angular-sanitize": "~1.7.4",
|
||||
"angular-touch": "~1.7.4",
|
||||
"angular-route": "~1.7.4",
|
||||
"angular-animate": "~1.7.4",
|
||||
"angular-i18n": "~1.7.4",
|
||||
"signalr": "^2.2.1",
|
||||
"typeahead.js": "~0.10.5",
|
||||
"underscore": "~1.9.1",
|
||||
|
||||
+97
-15
@@ -3,7 +3,7 @@
|
||||
|
||||
function ContentEditController($rootScope, $scope, $routeParams, $q, $window,
|
||||
appState, contentResource, entityResource, navigationService, notificationsService,
|
||||
serverValidationManager, contentEditingHelper, treeService, formHelper, umbRequestHelper,
|
||||
serverValidationManager, contentEditingHelper, treeService, formHelper, umbRequestHelper,
|
||||
editorState, $http, eventsService, relationResource, overlayService) {
|
||||
|
||||
var evts = [];
|
||||
@@ -56,11 +56,11 @@
|
||||
}
|
||||
|
||||
bindEvents();
|
||||
|
||||
resetVariantFlags();
|
||||
|
||||
resetVariantFlags();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This will reset isDirty flags if save is true.
|
||||
* When working with multiple variants, this will set the save/publish flags of each one to false.
|
||||
@@ -85,7 +85,7 @@
|
||||
$scope.content.variants[0].publish = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Returns true if the save/publish dialog should be shown when pressing the button */
|
||||
function showSaveOrPublishDialog() {
|
||||
return $scope.content.variants.length > 1;
|
||||
@@ -144,7 +144,7 @@
|
||||
*/
|
||||
function createButtons(content, app) {
|
||||
|
||||
// only create the save/publish/preview buttons if the
|
||||
// only create the save/publish/preview buttons if the
|
||||
// content app is "Conent"
|
||||
if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") {
|
||||
$scope.defaultButton = null;
|
||||
@@ -173,7 +173,7 @@
|
||||
schedulePublish: $scope.schedule
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$scope.defaultButton = buttons.defaultButton;
|
||||
$scope.subButtons = buttons.subButtons;
|
||||
$scope.page.showPreviewButton = true;
|
||||
@@ -203,7 +203,7 @@
|
||||
if (infiniteMode || !path) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!$scope.content.isChildOfListView) {
|
||||
navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
|
||||
$scope.page.menu.currentNode = syncArgs.node;
|
||||
@@ -214,7 +214,7 @@
|
||||
//it's a child item, just sync the ui node to the parent
|
||||
navigationService.syncTree({ tree: $scope.treeAlias, path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
|
||||
|
||||
//if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
|
||||
//if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
|
||||
// from the server so that we can load in the actions menu.
|
||||
umbRequestHelper.resourcePromise(
|
||||
$http.get(content.treeNodeUrl),
|
||||
@@ -224,9 +224,89 @@
|
||||
}
|
||||
}
|
||||
|
||||
function checkValidility(){
|
||||
//Get all controls from the 'contentForm'
|
||||
var allControls = $scope.contentForm.$getControls();
|
||||
|
||||
//An array to store items in when we find child form fields (no matter how many deep nested forms)
|
||||
var childFieldsToMarkAsValid = [];
|
||||
|
||||
//Exclude known formControls 'contentHeaderForm' and 'tabbedContentForm'
|
||||
//Check property - $name === "contentHeaderForm"
|
||||
allControls = _.filter(allControls, function(obj){
|
||||
return obj.$name !== 'contentHeaderForm' && obj.$name !== 'tabbedContentForm' && obj.hasOwnProperty('$submitted');
|
||||
});
|
||||
|
||||
for (var i = 0; i < allControls.length; i++) {
|
||||
var nestedForm = allControls[i];
|
||||
|
||||
//Get Nested Controls of this form in the loop
|
||||
var nestedFormControls = nestedForm.$getControls();
|
||||
|
||||
//Need to recurse through controls (could be more nested forms)
|
||||
childFieldsToMarkAsValid = recurseFormControls(nestedFormControls, childFieldsToMarkAsValid);
|
||||
}
|
||||
|
||||
return childFieldsToMarkAsValid;
|
||||
}
|
||||
|
||||
//Controls is the
|
||||
function recurseFormControls(controls, array){
|
||||
|
||||
//Loop over the controls
|
||||
for (var i = 0; i < controls.length; i++) {
|
||||
var controlItem = controls[i];
|
||||
|
||||
//Check if the controlItem has a property ''
|
||||
if(controlItem.hasOwnProperty('$submitted')){
|
||||
//This item is a form - so lets get the child controls of it & recurse again
|
||||
var childFormControls = controlItem.$getControls();
|
||||
recurseFormControls(childFormControls, array);
|
||||
}
|
||||
else {
|
||||
//We can assume its a field on a form
|
||||
if(controlItem.hasOwnProperty('$error')){
|
||||
//Set the validlity of the error/s to be valid
|
||||
//String of keys of error invalid messages
|
||||
var errorKeys = [];
|
||||
|
||||
for(var key in controlItem.$error){
|
||||
errorKeys.push(key);
|
||||
controlItem.$setValidity(key, true);
|
||||
}
|
||||
|
||||
//Create a basic obj - storing the control item & the error keys
|
||||
var obj = { 'control': controlItem, 'errorKeys': errorKeys };
|
||||
|
||||
//Push the updated control into the array - so we can set them back
|
||||
array.push(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function resetNestedFieldValiation(array){
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
var item = array[i];
|
||||
//Item is an object containing two props
|
||||
//'control' (obj) & 'errorKeys' (string array)
|
||||
var fieldControl = item.control;
|
||||
var fieldErrorKeys = item.errorKeys;
|
||||
|
||||
for(var i = 0; i < fieldErrorKeys.length; i++) {
|
||||
fieldControl.$setValidity(fieldErrorKeys[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
|
||||
function performSave(args) {
|
||||
|
||||
|
||||
//Used to check validility of nested form - coming from Content Apps mostly
|
||||
//Set them all to be invalid
|
||||
var fieldsToRollback = checkValidility();
|
||||
eventsService.emit("content.saving", { content: $scope.content, action: args.action });
|
||||
|
||||
return contentEditingHelper.contentEditorPerformSave({
|
||||
@@ -236,17 +316,17 @@
|
||||
action: args.action,
|
||||
showNotifications: args.showNotifications
|
||||
}).then(function (data) {
|
||||
//success
|
||||
//success
|
||||
init($scope.content);
|
||||
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
eventsService.emit("content.saved", { content: $scope.content, action: args.action });
|
||||
|
||||
resetNestedFieldValiation(fieldsToRollback);
|
||||
|
||||
return $q.when(data);
|
||||
},
|
||||
function (err) {
|
||||
|
||||
syncTreeNode($scope.content, $scope.content.path);
|
||||
|
||||
//error
|
||||
@@ -254,6 +334,8 @@
|
||||
editorState.set($scope.content);
|
||||
}
|
||||
|
||||
resetNestedFieldValiation(fieldsToRollback);
|
||||
|
||||
return $q.reject(err);
|
||||
});
|
||||
}
|
||||
@@ -388,7 +470,7 @@
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
overlayService.open(dialog);
|
||||
}
|
||||
}
|
||||
@@ -551,9 +633,9 @@
|
||||
|
||||
if (!$scope.busy) {
|
||||
|
||||
// Chromes popup blocker will kick in if a window is opened
|
||||
// Chromes popup blocker will kick in if a window is opened
|
||||
// without the initial scoped request. This trick will fix that.
|
||||
//
|
||||
//
|
||||
var previewWindow = $window.open('preview/?init=true', 'umbpreview');
|
||||
|
||||
// Build the correct path so both /#/ and #/ work.
|
||||
|
||||
+3
-2
@@ -186,14 +186,15 @@
|
||||
} else {
|
||||
vm.openVariants[editorIndex] = variant.language.culture;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//then assign the variant to a view model to the content app
|
||||
var contentApp = _.find(variant.apps, function (a) {
|
||||
return a.alias === "umbContent";
|
||||
});
|
||||
contentApp.viewModel = variant;
|
||||
|
||||
contentApp.viewModel = _.omit(variant, 'apps');
|
||||
|
||||
// make sure the same app it set to active in the new variant
|
||||
if(activeAppAlias) {
|
||||
|
||||
+18
-7
@@ -3,7 +3,7 @@
|
||||
* @name umbraco.directives.directive:valFormManager
|
||||
* @restrict A
|
||||
* @require formController
|
||||
* @description Used to broadcast an event to all elements inside this one to notify that form validation has
|
||||
* @description Used to broadcast an event to all elements inside this one to notify that form validation has
|
||||
* changed. If we don't use this that means you have to put a watch for each directive on a form's validation
|
||||
* changing which would result in much higher processing. We need to actually watch the whole $error collection of a form
|
||||
* because just watching $valid or $invalid doesn't acurrately trigger form validation changing.
|
||||
@@ -19,7 +19,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
var SAVED_EVENT_NAME = "formSubmitted";
|
||||
|
||||
return {
|
||||
require: ["form", "^^?valFormManager"],
|
||||
require: ["form", "^^?valFormManager", "^^?valSubView"],
|
||||
restrict: "A",
|
||||
controller: function($scope) {
|
||||
//This exposes an API for direct use with this directive
|
||||
@@ -46,9 +46,15 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
},
|
||||
link: function (scope, element, attr, ctrls) {
|
||||
|
||||
function notifySubView() {
|
||||
if (subView){
|
||||
subView.valStatusChanged({ form: formCtrl, showValidation: scope.showValidation });
|
||||
}
|
||||
}
|
||||
|
||||
var formCtrl = ctrls[0];
|
||||
var parentFormMgr = ctrls.length > 0 ? ctrls[1] : null;
|
||||
|
||||
var subView = ctrls.length > 1 ? ctrls[2] : null;
|
||||
var labels = {};
|
||||
|
||||
var labelKeys = [
|
||||
@@ -83,7 +89,9 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
return sum;
|
||||
}, function (e) {
|
||||
scope.$broadcast("valStatusChanged", { form: formCtrl });
|
||||
|
||||
|
||||
notifySubView();
|
||||
|
||||
//find all invalid elements' .control-group's and apply the error class
|
||||
var inError = element.find(".control-group .ng-invalid").closest(".control-group");
|
||||
inError.addClass("error");
|
||||
@@ -94,7 +102,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
|
||||
});
|
||||
|
||||
//This tracks if the user is currently saving a new item, we use this to determine
|
||||
//This tracks if the user is currently saving a new item, we use this to determine
|
||||
// if we should display the warning dialog that they are leaving the page - if a new item
|
||||
// is being saved we never want to display that dialog, this will also cause problems when there
|
||||
// are server side validation issues.
|
||||
@@ -104,6 +112,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.showValidation)) {
|
||||
element.addClass(SHOW_VALIDATION_CLASS_NAME);
|
||||
scope.showValidation = true;
|
||||
notifySubView();
|
||||
}
|
||||
|
||||
var unsubscribe = [];
|
||||
@@ -112,6 +121,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function(ev, args) {
|
||||
element.addClass(SHOW_VALIDATION_CLASS_NAME);
|
||||
scope.showValidation = true;
|
||||
notifySubView();
|
||||
//set the flag so we can check to see if we should display the error.
|
||||
isSavingNewItem = $routeParams.create;
|
||||
}));
|
||||
@@ -121,8 +131,9 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
//remove validation class
|
||||
element.removeClass(SHOW_VALIDATION_CLASS_NAME);
|
||||
scope.showValidation = false;
|
||||
notifySubView();
|
||||
//clear form state as at this point we retrieve new data from the server
|
||||
//and all validation will have cleared at this point
|
||||
//and all validation will have cleared at this point
|
||||
formCtrl.$setPristine();
|
||||
}));
|
||||
|
||||
@@ -203,7 +214,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
$timeout(function(){
|
||||
formCtrl.$setPristine();
|
||||
}, 1000);
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,29 @@
|
||||
|
||||
function valSubViewDirective() {
|
||||
|
||||
function controller($scope, $element) {
|
||||
//expose api
|
||||
return {
|
||||
valStatusChanged: function(args) {
|
||||
if (!args.form.$valid) {
|
||||
var subViewContent = $element.find(".ng-invalid");
|
||||
|
||||
if (subViewContent.length > 0) {
|
||||
$scope.model.hasError = true;
|
||||
$scope.model.errorClass = args.showValidation ? 'show-validation' : null;
|
||||
} else {
|
||||
$scope.model.hasError = false;
|
||||
$scope.model.errorClass = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$scope.model.hasError = false;
|
||||
$scope.model.errorClass = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
//if there are no containing form or valFormManager controllers, then we do nothing
|
||||
@@ -43,7 +66,8 @@
|
||||
var directive = {
|
||||
require: ['?^^form', '?^^valFormManager'],
|
||||
restrict: "A",
|
||||
link: link
|
||||
link: link,
|
||||
controller: controller
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<ul class="umb-sub-views-nav" ng-show="showNavigation">
|
||||
|
||||
|
||||
<li ng-repeat="item in navigation | limitTo: itemsLimit ">
|
||||
<div ng-show="item.alias !== 'more'">
|
||||
<div ng-show="item.alias !== 'more'"
|
||||
ng-class="item.errorClass">
|
||||
<a data-element="sub-view-{{item.alias}}"
|
||||
tabindex="-1"
|
||||
class="umb-sub-views-nav-item js-umb-sub-views-nav-item"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<div class="umb-editor-sub-view"
|
||||
ng-class="'sub-view-' + model.name"
|
||||
val-sub-view>
|
||||
ng-class="'sub-view-' + model.name"
|
||||
val-sub-view>
|
||||
|
||||
<div class="umb-editor-sub-view__content"
|
||||
ng-show="model.active === true"
|
||||
ng-include="model.view">
|
||||
<div
|
||||
class="umb-editor-sub-view__content"
|
||||
ng-show="model.active === true"
|
||||
ng-include="model.view">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
<umb-box>
|
||||
<umb-box-content>
|
||||
<h3 class="bold">Hours of Umbraco training videos are only a click away</h3>
|
||||
<p>Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco, then visit <a class="btn-link -underline" href="https://umbraco.tv" target="_blank">umbraco.tv</a> for even more Umbraco videos.</p>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<div ng-init="init('https://umbraco.tv/videos/developer/chapterrss?sort=no')"
|
||||
ng-controller="Umbraco.Dashboard.StartupVideosController">
|
||||
|
||||
<div class="umb-getstartedcards">
|
||||
<a class="umb-getstartedcard" ng-repeat="video in videos" href="{{video.link}}" title="{{video.title}}" target="_blank" rel="noopener">
|
||||
<img ng-src="{{video.thumbnail}}" alt="{{video.title}}">
|
||||
<div class="umb-getstartedbody">
|
||||
<p>{{video.title}}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -29,7 +29,10 @@
|
||||
"languages_mandatoryLanguageHelp",
|
||||
"languages_defaultLanguage",
|
||||
"languages_defaultLanguageHelp",
|
||||
"languages_addLanguage"
|
||||
"languages_addLanguage",
|
||||
"languages_noFallbackLanguageOption",
|
||||
"languages_fallbackLanguageDescription",
|
||||
"languages_fallbackLanguage"
|
||||
];
|
||||
|
||||
localizationService.localizeMany(labelKeys).then(function (values) {
|
||||
@@ -39,8 +42,17 @@
|
||||
vm.labels.defaultLanguage = values[3];
|
||||
vm.labels.defaultLanguageHelp = values[4];
|
||||
vm.labels.addLanguage = values[5];
|
||||
vm.labels.noFallbackLanguageOption = values[6];
|
||||
|
||||
if($routeParams.create) {
|
||||
$scope.properties = {
|
||||
fallbackLanguage: {
|
||||
alias: "fallbackLanguage",
|
||||
description: values[7],
|
||||
label: values[8]
|
||||
}
|
||||
};
|
||||
|
||||
if ($routeParams.create) {
|
||||
vm.page.name = vm.labels.addLanguage;
|
||||
languageResource.getCultures().then(function (culturesDictionary) {
|
||||
var cultures = [];
|
||||
@@ -53,10 +65,17 @@
|
||||
vm.availableCultures = cultures;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if(!$routeParams.create) {
|
||||
vm.loading = true;
|
||||
languageResource.getAll().then(function (languages) {
|
||||
vm.availableLanguages = languages.filter(function (l) {
|
||||
return $routeParams.id != l.id;
|
||||
});
|
||||
vm.loading = false;
|
||||
});
|
||||
|
||||
if (!$routeParams.create) {
|
||||
|
||||
vm.loading = true;
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
hide-alias="true">
|
||||
</umb-editor-header>
|
||||
|
||||
<umb-editor-container>
|
||||
<umb-editor-container class="form-horizontal">
|
||||
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<umb-box ng-if="!vm.loading" class="block-form">
|
||||
<umb-box ng-if="!vm.loading">
|
||||
<umb-box-content>
|
||||
|
||||
<umb-control-group label="@general_language" ng-if="vm.availableCultures">
|
||||
@@ -64,6 +64,16 @@
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-property property="properties.fallbackLanguage">
|
||||
<div>
|
||||
<select name="fallbackLanguage"
|
||||
ng-model="vm.language.fallbackLanguageId"
|
||||
ng-options="l.id as l.name for l in vm.availableLanguages">
|
||||
<option value="">{{vm.labels.noFallbackLanguageOption}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</umb-property>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
|
||||
@@ -13,6 +13,16 @@
|
||||
vm.editLanguage = editLanguage;
|
||||
vm.deleteLanguage = deleteLanguage;
|
||||
|
||||
vm.getLanguageById = function(id) {
|
||||
for (var i = 0; i < vm.languages.length; i++) {
|
||||
if (vm.languages[i].id === id) {
|
||||
return vm.languages[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
function init() {
|
||||
|
||||
vm.loading = true;
|
||||
@@ -21,18 +31,20 @@
|
||||
var labelKeys = [
|
||||
"treeHeaders_languages",
|
||||
"general_mandatory",
|
||||
"general_default"
|
||||
"general_default",
|
||||
"languages_fallsbackToLabel"
|
||||
];
|
||||
|
||||
localizationService.localizeMany(labelKeys).then(function (values) {
|
||||
vm.labels.languages = values[0];
|
||||
vm.labels.mandatory = values[1];
|
||||
vm.labels.general = values[2];
|
||||
vm.labels.fallsbackTo = values[3];
|
||||
// set page name
|
||||
vm.page.name = vm.labels.languages;
|
||||
});
|
||||
|
||||
languageResource.getAll().then(function(languages) {
|
||||
languageResource.getAll().then(function (languages) {
|
||||
vm.languages = languages;
|
||||
vm.loading = false;
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<th>Language</th>
|
||||
<th>Default</th>
|
||||
<th>Mandatory</th>
|
||||
<th>Fallback</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -55,6 +56,10 @@
|
||||
size="xs">
|
||||
</umb-checkmark>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="language.fallbackLanguageId">{{vm.getLanguageById(language.fallbackLanguageId).name}}</span>
|
||||
<span ng-if="!language.fallbackLanguageId">(none)</span>
|
||||
</td>
|
||||
<td style="text-align: right;">
|
||||
<umb-button
|
||||
ng-if="!language.isDefault"
|
||||
|
||||
@@ -27,25 +27,28 @@
|
||||
{
|
||||
"name": "Packages",
|
||||
"icon": "icon-cloud",
|
||||
"view": "views/packager/views/repo.html",
|
||||
"active": !installPackageUri || installPackageUri === "navigation"
|
||||
"view": "views/packages/views/repo.html",
|
||||
"active": !installPackageUri || installPackageUri === "navigation",
|
||||
"alias": "umbPackages"
|
||||
},
|
||||
{
|
||||
"name": "Installed",
|
||||
"icon": "icon-box",
|
||||
"view": "views/packager/views/installed.html",
|
||||
"active": installPackageUri === "installed"
|
||||
"view": "views/packages/views/installed.html",
|
||||
"active": installPackageUri === "installed",
|
||||
"alias": "umbInstalled"
|
||||
},
|
||||
{
|
||||
"name": "Install local",
|
||||
"icon": "icon-add",
|
||||
"view": "views/packager/views/install-local.html",
|
||||
"active": installPackageUri === "local"
|
||||
"view": "views/packages/views/install-local.html",
|
||||
"active": installPackageUri === "local",
|
||||
"alias": "umbInstallLocal"
|
||||
}
|
||||
];
|
||||
|
||||
$timeout(function () {
|
||||
navigationService.syncTree({ tree: "packager", path: "-1" });
|
||||
navigationService.syncTree({ tree: "packages", path: "-1" });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<umb-editor-container>
|
||||
|
||||
<umb-editor-sub-views
|
||||
sub-views="vm.page.navigation">
|
||||
sub-views="vm.page.navigation" model="vm.page">
|
||||
</umb-editor-sub-views>
|
||||
|
||||
</umb-editor-container>
|
||||
|
||||
@@ -1713,10 +1713,14 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<area alias="languages">
|
||||
<key alias="addLanguage">Add language</key>
|
||||
<key alias="mandatoryLanguage">Mandatory language</key>
|
||||
<key alias="mandatoryLanguageHelp">Properties on this language has to be filled out before the node can be published.</key>
|
||||
<key alias="mandatoryLanguageHelp">Properties on this language have to be filled out before the node can be published.</key>
|
||||
<key alias="defaultLanguage">Default language</key>
|
||||
<key alias="defaultLanguageHelp">An Umbraco site can only have one default language set.</key>
|
||||
<key alias="changingDefaultLanguageWarning">Switching default language may result in default content missing.</key>
|
||||
<key alias="fallsbackToLabel">Falls back to</key>
|
||||
<key alias="noFallbackLanguageOption">No fall back language</key>
|
||||
<key alias="fallbackLanguageDescription">To allow multi-lingual content to fall back to another language if not present in the requested language, select it here.</key>
|
||||
<key alias="fallbackLanguage">Fall back language</key>
|
||||
</area>
|
||||
|
||||
<area alias="modelsBuilder">
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
views/dashboard/settings/settingsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Examine Management">
|
||||
<control>
|
||||
views/dashboard/settings/examinemanagement.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Published Status">
|
||||
<control>
|
||||
views/dashboard/settings/publishedstatus.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
|
||||
<section alias="StartupFormsDashboardSection">
|
||||
@@ -27,21 +37,6 @@
|
||||
<areas>
|
||||
<area>developer</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/developer/developerdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Examine Management">
|
||||
<control>
|
||||
views/dashboard/developer/examinemanagement.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Published Status">
|
||||
<control>
|
||||
views/dashboard/developer/publishedstatus.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
|
||||
<section alias="StartupMediaDashboardSection">
|
||||
@@ -99,11 +94,11 @@
|
||||
|
||||
<section alias="UmbracoHealthCheck">
|
||||
<areas>
|
||||
<area>developer</area>
|
||||
<area>settings</area>
|
||||
</areas>
|
||||
<tab caption="Health Check">
|
||||
<control>
|
||||
views/dashboard/developer/healthcheck.html
|
||||
views/dashboard/settings/healthcheck.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
@@ -113,7 +108,7 @@
|
||||
</areas>
|
||||
<tab caption="Redirect URL Management">
|
||||
<control>
|
||||
views/dashboard/developer/redirecturls.html
|
||||
views/dashboard/content/redirecturls.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
|
||||
@@ -1,127 +1,122 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<dashBoard>
|
||||
<section alias="StartupSettingsDashboardSection">
|
||||
<areas>
|
||||
<area>settings</area>
|
||||
</areas>
|
||||
<tab caption="Welcome">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/settings/settingsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupFormsDashboardSection">
|
||||
<areas>
|
||||
<area>forms</area>
|
||||
</areas>
|
||||
<tab caption="Install Umbraco Forms">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/forms/formsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupDeveloperDashboardSection">
|
||||
<areas>
|
||||
<area>developer</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/developer/developerdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Examine Management">
|
||||
<control>
|
||||
views/dashboard/developer/examinemanagement.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Published Status">
|
||||
<control>
|
||||
views/dashboard/developer/publishedstatus.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupMediaDashboardSection">
|
||||
<areas>
|
||||
<area>media</area>
|
||||
</areas>
|
||||
<tab caption="Content">
|
||||
<control showOnce="false" addPanel="false" panelCaption="">
|
||||
views/dashboard/media/mediafolderbrowser.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupFormsDashboardSection">
|
||||
<areas>
|
||||
<area>forms</area>
|
||||
</areas>
|
||||
<tab caption="Install Umbraco Forms">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/forms/formsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupDashboardSection">
|
||||
<access>
|
||||
<deny>translator</deny>
|
||||
</access>
|
||||
<areas>
|
||||
<area>content</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<access>
|
||||
<grant>admin</grant>
|
||||
</access>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/default/startupdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupMemberDashboardSection">
|
||||
<areas>
|
||||
<area>member</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/members/membersdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="contour">
|
||||
<areas>
|
||||
<area>contour</area>
|
||||
</areas>
|
||||
<tab caption="Dashboard">
|
||||
<control>plugins/umbracocontour/formsdashboard.ascx</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="UmbracoHealthCheck">
|
||||
<areas>
|
||||
<area>developer</area>
|
||||
</areas>
|
||||
<tab caption="Health Check">
|
||||
<control>
|
||||
views/dashboard/developer/healthcheck.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="RedirectUrlManagement">
|
||||
<areas>
|
||||
<area>content</area>
|
||||
</areas>
|
||||
<tab caption="Redirect URL Management">
|
||||
<control>
|
||||
views/dashboard/developer/redirecturls.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="UmbracoModelsBuilder">
|
||||
<areas>
|
||||
<area>developer</area>
|
||||
</areas>
|
||||
<tab caption="Models Builder">
|
||||
<control>
|
||||
/App_Plugins/ModelsBuilder/modelsbuilder.htm
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
</dashBoard>
|
||||
<section alias="StartupSettingsDashboardSection">
|
||||
<areas>
|
||||
<area>settings</area>
|
||||
</areas>
|
||||
<tab caption="Welcome">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/settings/settingsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Examine Management">
|
||||
<control>
|
||||
views/dashboard/settings/examinemanagement.html
|
||||
</control>
|
||||
</tab>
|
||||
<tab caption="Published Status">
|
||||
<control>
|
||||
views/dashboard/settings/publishedstatus.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupFormsDashboardSection">
|
||||
<areas>
|
||||
<area>forms</area>
|
||||
</areas>
|
||||
<tab caption="Install Umbraco Forms">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/forms/formsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupDeveloperDashboardSection">
|
||||
<areas>
|
||||
<area>developer</area>
|
||||
</areas>
|
||||
</section>
|
||||
<section alias="StartupMediaDashboardSection">
|
||||
<areas>
|
||||
<area>media</area>
|
||||
</areas>
|
||||
<tab caption="Content">
|
||||
<control showOnce="false" addPanel="false" panelCaption="">
|
||||
views/dashboard/media/mediafolderbrowser.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupFormsDashboardSection">
|
||||
<areas>
|
||||
<area>forms</area>
|
||||
</areas>
|
||||
<tab caption="Install Umbraco Forms">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/forms/formsdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupDashboardSection">
|
||||
<access>
|
||||
<deny>translator</deny>
|
||||
</access>
|
||||
<areas>
|
||||
<area>content</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<access>
|
||||
<grant>admin</grant>
|
||||
</access>
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/default/startupdashboardintro.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="StartupMemberDashboardSection">
|
||||
<areas>
|
||||
<area>member</area>
|
||||
</areas>
|
||||
<tab caption="Get Started">
|
||||
<control showOnce="true" addPanel="true" panelCaption="">
|
||||
views/dashboard/members/membersdashboardvideos.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="contour">
|
||||
<areas>
|
||||
<area>contour</area>
|
||||
</areas>
|
||||
<tab caption="Dashboard">
|
||||
<control>plugins/umbracocontour/formsdashboard.ascx</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="UmbracoHealthCheck">
|
||||
<areas>
|
||||
<area>settings</area>
|
||||
</areas>
|
||||
<tab caption="Health Check">
|
||||
<control>
|
||||
views/dashboard/settings/healthcheck.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="RedirectUrlManagement">
|
||||
<areas>
|
||||
<area>content</area>
|
||||
</areas>
|
||||
<tab caption="Redirect URL Management">
|
||||
<control>
|
||||
views/dashboard/content/redirecturls.html
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
<section alias="UmbracoModelsBuilder">
|
||||
<areas>
|
||||
<area>settings</area>
|
||||
</areas>
|
||||
<tab caption="Models Builder">
|
||||
<control>
|
||||
/App_Plugins/ModelsBuilder/modelsbuilder.htm
|
||||
</control>
|
||||
</tab>
|
||||
</section>
|
||||
</dashBoard>
|
||||
|
||||
@@ -249,6 +249,8 @@ namespace Umbraco.Web.Composing
|
||||
|
||||
public static IPublishedValueFallback PublishedValueFallback => CoreCurrent.PublishedValueFallback;
|
||||
|
||||
public static IVariationContextAccessor VariationContextAccessor => CoreCurrent.VariationContextAccessor;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,7 +877,7 @@ namespace Umbraco.Web.Editors
|
||||
var canPublish = true;
|
||||
|
||||
//validate any mandatory variants that are not in the list
|
||||
var mandatoryLangs = Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(_allLangs.Value.Values).Where(x => x.Mandatory);
|
||||
var mandatoryLangs = Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(_allLangs.Value.Values).Where(x => x.IsMandatory);
|
||||
|
||||
foreach (var lang in mandatoryLangs)
|
||||
{
|
||||
|
||||
@@ -46,49 +46,52 @@ namespace Umbraco.Web.Editors
|
||||
var tabs = new List<Tab<DashboardControl>>();
|
||||
var i = 1;
|
||||
|
||||
// The dashboard config can contain more than one area inserted by a package.
|
||||
foreach (var dashboardSection in UmbracoConfig.For.DashboardSettings().Sections.Where(x => x.Areas.Contains(section)))
|
||||
{
|
||||
//we need to validate access to this section
|
||||
if (DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
//disable packages section dashboard
|
||||
if (section != "packages")
|
||||
|
||||
//User is authorized
|
||||
foreach (var tab in dashboardSection.Tabs)
|
||||
// The dashboard config can contain more than one area inserted by a package.
|
||||
foreach (var dashboardSection in UmbracoConfig.For.DashboardSettings().Sections.Where(x => x.Areas.Contains(section)))
|
||||
{
|
||||
//we need to validate access to this tab
|
||||
if (DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService) == false)
|
||||
//we need to validate access to this section
|
||||
if (DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControls = new List<DashboardControl>();
|
||||
|
||||
foreach (var control in tab.Controls)
|
||||
//User is authorized
|
||||
foreach (var tab in dashboardSection.Tabs)
|
||||
{
|
||||
if (DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService) == false)
|
||||
//we need to validate access to this tab
|
||||
if (DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControl = new DashboardControl();
|
||||
var controlPath = control.ControlPath.Trim();
|
||||
dashboardControl.Caption = control.PanelCaption;
|
||||
dashboardControl.Path = IOHelper.FindFile(controlPath);
|
||||
if (controlPath.ToLowerInvariant().EndsWith(".ascx".ToLowerInvariant()))
|
||||
dashboardControl.ServerSide = true;
|
||||
var dashboardControls = new List<DashboardControl>();
|
||||
|
||||
dashboardControls.Add(dashboardControl);
|
||||
foreach (var control in tab.Controls)
|
||||
{
|
||||
if (DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControl = new DashboardControl();
|
||||
var controlPath = control.ControlPath.Trim();
|
||||
dashboardControl.Caption = control.PanelCaption;
|
||||
dashboardControl.Path = IOHelper.FindFile(controlPath);
|
||||
if (controlPath.ToLowerInvariant().EndsWith(".ascx".ToLowerInvariant()))
|
||||
dashboardControl.ServerSide = true;
|
||||
|
||||
dashboardControls.Add(dashboardControl);
|
||||
}
|
||||
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = i,
|
||||
Alias = tab.Caption.ToSafeAlias(),
|
||||
IsActive = i == 1,
|
||||
Label = tab.Caption,
|
||||
Properties = dashboardControls
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = i,
|
||||
Alias = tab.Caption.ToSafeAlias(),
|
||||
IsActive = i == 1,
|
||||
Label = tab.Caption,
|
||||
Properties = dashboardControls
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
//In case there are no tabs or a user doesn't have access the empty tabs list is returned
|
||||
return tabs;
|
||||
|
||||
@@ -65,15 +65,21 @@ namespace Umbraco.Web.Editors
|
||||
public IHttpActionResult DeleteLanguage(int id)
|
||||
{
|
||||
var language = Services.LocalizationService.GetLanguageById(id);
|
||||
if (language == null) return NotFound();
|
||||
if (language == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// the service would not let us do it, but test here nevertheless
|
||||
if (language.IsDefaultVariantLanguage)
|
||||
if (language.IsDefault)
|
||||
{
|
||||
var message = $"Language '{language.IsoCode}' is currently set to 'default' and can not be deleted.";
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(message));
|
||||
}
|
||||
|
||||
// service is happy deleting a language that's fallback for another language,
|
||||
// will just remove it - so no need to check here
|
||||
|
||||
Services.LocalizationService.Delete(language);
|
||||
|
||||
return Ok();
|
||||
@@ -112,31 +118,66 @@ namespace Umbraco.Web.Editors
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//create it
|
||||
var newLang = new Umbraco.Core.Models.Language(culture.Name)
|
||||
// create it (creating a new language cannot create a fallback cycle)
|
||||
var newLang = new Core.Models.Language(culture.Name)
|
||||
{
|
||||
CultureName = culture.DisplayName,
|
||||
IsDefaultVariantLanguage = language.IsDefaultVariantLanguage,
|
||||
Mandatory = language.Mandatory
|
||||
IsDefault = language.IsDefault,
|
||||
IsMandatory = language.IsMandatory,
|
||||
FallbackLanguageId = language.FallbackLanguageId
|
||||
};
|
||||
|
||||
Services.LocalizationService.Save(newLang);
|
||||
return Mapper.Map<Language>(newLang);
|
||||
}
|
||||
|
||||
existing.Mandatory = language.Mandatory;
|
||||
existing.IsMandatory = language.IsMandatory;
|
||||
|
||||
// note that the service will prevent the default language from being "un-defaulted"
|
||||
// but does not hurt to test here - though the UI should prevent it too
|
||||
if (existing.IsDefaultVariantLanguage && !language.IsDefaultVariantLanguage)
|
||||
if (existing.IsDefault && !language.IsDefault)
|
||||
{
|
||||
ModelState.AddModelError("IsDefaultVariantLanguage", "Cannot un-default the default language.");
|
||||
ModelState.AddModelError("IsDefault", "Cannot un-default the default language.");
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
existing.IsDefaultVariantLanguage = language.IsDefaultVariantLanguage;
|
||||
existing.IsDefault = language.IsDefault;
|
||||
existing.FallbackLanguageId = language.FallbackLanguageId;
|
||||
|
||||
// modifying an existing language can create a fallback, verify
|
||||
// note that the service will check again, dealing with race conds
|
||||
if (existing.FallbackLanguageId.HasValue)
|
||||
{
|
||||
var languages = Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.Id, x => x);
|
||||
if (!languages.ContainsKey(existing.FallbackLanguageId.Value))
|
||||
{
|
||||
ModelState.AddModelError("FallbackLanguage", "The selected fall back language does not exist.");
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
|
||||
}
|
||||
if (CreatesCycle(existing, languages))
|
||||
{
|
||||
ModelState.AddModelError("FallbackLanguage", $"The selected fall back language {languages[existing.FallbackLanguageId.Value].IsoCode} would create a circular path.");
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
|
||||
}
|
||||
}
|
||||
|
||||
Services.LocalizationService.Save(existing);
|
||||
return Mapper.Map<Language>(existing);
|
||||
}
|
||||
}
|
||||
|
||||
// see LocalizationService
|
||||
private bool CreatesCycle(ILanguage language, IDictionary<int, ILanguage> languages)
|
||||
{
|
||||
// a new language is not referenced yet, so cannot be part of a cycle
|
||||
if (!language.HasIdentity) return false;
|
||||
|
||||
var id = language.FallbackLanguageId;
|
||||
while (true) // assuming languages does not already contains a cycle, this must end
|
||||
{
|
||||
if (!id.HasValue) return false; // no fallback means no cycle
|
||||
if (id.Value == language.Id) return true; // back to language = cycle!
|
||||
id = languages[id.Value].FallbackLanguageId; // else keep chaining
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ namespace Umbraco.Web.Editors
|
||||
public string GetPublishedStatusUrl()
|
||||
{
|
||||
if (_publishedSnapshotService is PublishedCache.XmlPublishedCache.PublishedSnapshotService)
|
||||
return "views/dashboard/developer/xmldataintegrityreport.html";
|
||||
return "views/dashboard/settings/xmldataintegrityreport.html";
|
||||
|
||||
//if (service is PublishedCache.PublishedNoCache.PublishedSnapshotService)
|
||||
// return "views/dashboard/developer/nocache.html";
|
||||
|
||||
if (_publishedSnapshotService is PublishedCache.NuCache.PublishedSnapshotService)
|
||||
return "views/dashboard/developer/nucache.html";
|
||||
return "views/dashboard/settings/nucache.html";
|
||||
|
||||
throw new NotSupportedException("Not supported: " + _publishedSnapshotService.GetType().FullName);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Web.HealthCheck
|
||||
/// <summary>
|
||||
/// The API controller used to display the health check info and execute any actions
|
||||
/// </summary>
|
||||
[UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)]
|
||||
[UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)]
|
||||
public class HealthCheckController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly HealthCheckCollection _checks;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
@@ -20,9 +19,12 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "isDefault")]
|
||||
public bool IsDefaultVariantLanguage { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
[DataMember(Name = "isMandatory")]
|
||||
public bool Mandatory { get; set; }
|
||||
public bool IsMandatory { get; set; }
|
||||
|
||||
[DataMember(Name = "fallbackLanguageId")]
|
||||
public int? FallbackLanguageId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Models.PublishedContent
|
||||
{
|
||||
@@ -7,86 +11,283 @@ namespace Umbraco.Web.Models.PublishedContent
|
||||
/// </summary>
|
||||
public class PublishedValueFallback : IPublishedValueFallback
|
||||
{
|
||||
// this is our default implementation
|
||||
// kinda reproducing what was available in v7
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedValueFallback"/> class.
|
||||
/// </summary>
|
||||
public PublishedValueFallback(ServiceContext serviceContext, IVariationContextAccessor variationContextAccessor)
|
||||
{
|
||||
// no fallback here
|
||||
return defaultValue;
|
||||
_localizationService = serviceContext.LocalizationService;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue)
|
||||
public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
// no fallback here
|
||||
return defaultValue;
|
||||
return TryGetValue<object>(property, culture, segment, fallback, defaultValue, out value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue)
|
||||
public bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
// no fallback here
|
||||
return defaultValue;
|
||||
_variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment);
|
||||
|
||||
foreach (var f in fallback)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case Fallback.None:
|
||||
continue;
|
||||
case Fallback.DefaultValue:
|
||||
value = defaultValue;
|
||||
return true;
|
||||
case Fallback.Language:
|
||||
if (TryGetValueWithLanguageFallback(property, culture, segment, defaultValue, out value))
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
throw NotSupportedFallbackMethod(f, "property");
|
||||
}
|
||||
}
|
||||
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue)
|
||||
public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
// no fallback here
|
||||
return defaultValue;
|
||||
return TryGetValue<object>(content, alias, culture, segment, fallback, defaultValue, out value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse)
|
||||
public bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
// no fallback here
|
||||
if (!recurse) return defaultValue;
|
||||
var propertyType = content.ContentType.GetPropertyType(alias);
|
||||
if (propertyType == null)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
_variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment);
|
||||
|
||||
foreach (var f in fallback)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case Fallback.None:
|
||||
continue;
|
||||
case Fallback.DefaultValue:
|
||||
value = defaultValue;
|
||||
return true;
|
||||
case Fallback.Language:
|
||||
if (TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out value))
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
throw NotSupportedFallbackMethod(f, "element");
|
||||
}
|
||||
}
|
||||
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
||||
{
|
||||
// is that ok?
|
||||
return GetValue<object>(content, alias, culture, segment, defaultValue, recurse);
|
||||
return TryGetValue<object>(content, alias, culture, segment, fallback, defaultValue, out value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse)
|
||||
public virtual bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
||||
{
|
||||
// no fallback here
|
||||
if (!recurse) return defaultValue;
|
||||
var propertyType = content.ContentType.GetPropertyType(alias);
|
||||
if (propertyType == null)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
_variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment);
|
||||
|
||||
// otherwise, implement recursion as it was implemented in PublishedContentBase
|
||||
// note: we don't support "recurse & language" which would walk up the tree,
|
||||
// looking at languages at each level - should someone need it... they'll have
|
||||
// to implement it.
|
||||
|
||||
// fixme caching?
|
||||
//
|
||||
// all caches were using PublishedContentBase.GetProperty(alias, recurse) to get the property,
|
||||
// then,
|
||||
// NuCache.PublishedContent was storing the property in GetAppropriateCache() with key "NuCache.Property.Recurse[" + DraftOrPub(previewing) + contentUid + ":" + typeAlias + "]";
|
||||
// XmlPublishedContent was storing the property in _cacheProvider with key $"XmlPublishedCache.PublishedContentCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}";
|
||||
// DictionaryPublishedContent was storing the property in _cacheProvider with key $"XmlPublishedCache.PublishedMediaCache:RecursiveProperty-{Id}-{alias.ToLowerInvariant()}";
|
||||
//
|
||||
// at the moment, caching has been entirely removed, until we better understand caching + fallback
|
||||
foreach (var f in fallback)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case Fallback.None:
|
||||
continue;
|
||||
case Fallback.DefaultValue:
|
||||
value = defaultValue;
|
||||
return true;
|
||||
case Fallback.Language:
|
||||
if (TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out value))
|
||||
return true;
|
||||
break;
|
||||
case Fallback.Ancestors:
|
||||
if (TryGetValueWithRecursiveFallback(content, alias, culture, segment, defaultValue, out value))
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
throw NotSupportedFallbackMethod(f, "content");
|
||||
}
|
||||
}
|
||||
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
private NotSupportedException NotSupportedFallbackMethod(int fallback, string level)
|
||||
{
|
||||
return new NotSupportedException($"Fallback {GetType().Name} does not support fallback code '{fallback}' at {level} level.");
|
||||
}
|
||||
|
||||
// tries to get a value, recursing the tree
|
||||
private static bool TryGetValueWithRecursiveFallback<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, out T value)
|
||||
{
|
||||
IPublishedProperty property = null; // if we are here, content's property has no value
|
||||
IPublishedProperty noValueProperty = null;
|
||||
do
|
||||
{
|
||||
content = content.Parent;
|
||||
property = content?.GetProperty(alias);
|
||||
if (property != null) noValueProperty = property;
|
||||
} while (content != null && (property == null || property.HasValue(culture, segment) == false));
|
||||
if (property != null)
|
||||
{
|
||||
noValueProperty = property;
|
||||
}
|
||||
}
|
||||
while (content != null && (property == null || property.HasValue(culture, segment) == false));
|
||||
|
||||
// if we found a content with the property having a value, return that property value
|
||||
if (property != null && property.HasValue(culture, segment))
|
||||
return property.Value<T>(culture, segment);
|
||||
{
|
||||
value = property.Value<T>(culture, segment);
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we found a property, even though with no value, return that property value
|
||||
// because the converter may want to handle the missing value. ie if defaultValue is default,
|
||||
// either specified or by default, the converter may want to substitute something else.
|
||||
if (noValueProperty != null)
|
||||
return noValueProperty.Value<T>(culture, segment, defaultValue: defaultValue);
|
||||
{
|
||||
value = noValueProperty.Value<T>(culture, segment, defaultValue: defaultValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// else return default
|
||||
return defaultValue;
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
// tries to get a value, falling back onto other languages
|
||||
private bool TryGetValueWithLanguageFallback<T>(IPublishedProperty property, string culture, string segment, T defaultValue, out T value)
|
||||
{
|
||||
value = defaultValue;
|
||||
|
||||
if (culture.IsNullOrWhiteSpace()) return false;
|
||||
|
||||
var visited = new HashSet<int>();
|
||||
|
||||
var language = _localizationService.GetLanguageByIsoCode(culture);
|
||||
if (language == null) return false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (language.FallbackLanguageId == null) return false;
|
||||
|
||||
var language2Id = language.FallbackLanguageId.Value;
|
||||
if (visited.Contains(language2Id)) return false;
|
||||
visited.Add(language2Id);
|
||||
|
||||
var language2 = _localizationService.GetLanguageById(language2Id);
|
||||
if (language2 == null) return false;
|
||||
var culture2 = language2.IsoCode;
|
||||
|
||||
if (property.HasValue(culture2, segment))
|
||||
{
|
||||
value = property.Value<T>(culture2, segment);
|
||||
return true;
|
||||
}
|
||||
|
||||
language = language2;
|
||||
}
|
||||
}
|
||||
|
||||
// tries to get a value, falling back onto other languages
|
||||
private bool TryGetValueWithLanguageFallback<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue, out T value)
|
||||
{
|
||||
value = defaultValue;
|
||||
|
||||
if (culture.IsNullOrWhiteSpace()) return false;
|
||||
|
||||
var visited = new HashSet<int>();
|
||||
|
||||
var language = _localizationService.GetLanguageByIsoCode(culture);
|
||||
if (language == null) return false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (language.FallbackLanguageId == null) return false;
|
||||
|
||||
var language2Id = language.FallbackLanguageId.Value;
|
||||
if (visited.Contains(language2Id)) return false;
|
||||
visited.Add(language2Id);
|
||||
|
||||
var language2 = _localizationService.GetLanguageById(language2Id);
|
||||
if (language2 == null) return false;
|
||||
var culture2 = language2.IsoCode;
|
||||
|
||||
if (content.HasValue(alias, culture2, segment))
|
||||
{
|
||||
value = content.Value<T>(alias, culture2, segment);
|
||||
return true;
|
||||
}
|
||||
|
||||
language = language2;
|
||||
}
|
||||
}
|
||||
|
||||
// tries to get a value, falling back onto other languages
|
||||
private bool TryGetValueWithLanguageFallback<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, out T value)
|
||||
{
|
||||
value = defaultValue;
|
||||
|
||||
if (culture.IsNullOrWhiteSpace()) return false;
|
||||
|
||||
var visited = new HashSet<int>();
|
||||
|
||||
// fixme
|
||||
// _localizationService.GetXxx() is expensive, it deep clones objects
|
||||
// we want _localizationService.GetReadOnlyXxx() returning IReadOnlyLanguage which cannot be saved back = no need to clone
|
||||
|
||||
var language = _localizationService.GetLanguageByIsoCode(culture);
|
||||
if (language == null) return false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (language.FallbackLanguageId == null) return false;
|
||||
|
||||
var language2Id = language.FallbackLanguageId.Value;
|
||||
if (visited.Contains(language2Id)) return false;
|
||||
visited.Add(language2Id);
|
||||
|
||||
var language2 = _localizationService.GetLanguageById(language2Id);
|
||||
if (language2 == null) return false;
|
||||
var culture2 = language2.IsoCode;
|
||||
|
||||
if (content.HasValue(alias, culture2, segment))
|
||||
{
|
||||
value = content.Value<T>(alias, culture2, segment);
|
||||
return true;
|
||||
}
|
||||
|
||||
language = language2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,16 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||
}
|
||||
|
||||
public override Type GetPropertyValueType(PublishedPropertyType propertyType)
|
||||
=> IsMultipleDataType(propertyType.DataType)
|
||||
? typeof (IEnumerable<IPublishedContent>)
|
||||
: typeof (IPublishedContent);
|
||||
{
|
||||
var isMultiple = IsMultipleDataType(propertyType.DataType);
|
||||
var isOnlyImages = IsOnlyImagesDataType(propertyType.DataType);
|
||||
|
||||
// hard-coding "image" here but that's how it works at UI level too
|
||||
|
||||
return isMultiple
|
||||
? (isOnlyImages ? typeof(IEnumerable<>).MakeGenericType(ModelType.For("image")) : typeof(IEnumerable<IPublishedContent>))
|
||||
: (isOnlyImages ? ModelType.For("image") : typeof(IPublishedContent));
|
||||
}
|
||||
|
||||
public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType)
|
||||
=> PropertyCacheLevel.Snapshot;
|
||||
@@ -40,6 +47,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||
return config.Multiple;
|
||||
}
|
||||
|
||||
private bool IsOnlyImagesDataType(PublishedDataType dataType)
|
||||
{
|
||||
var config = ConfigurationEditor.ConfigurationAs<MediaPickerConfiguration>(dataType.Configuration);
|
||||
return config.OnlyImages;
|
||||
}
|
||||
|
||||
public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview)
|
||||
{
|
||||
if (source == null) return null;
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
// determines whether a property has value
|
||||
public override bool HasValue(string culture = null, string segment = null)
|
||||
{
|
||||
ContextualizeVariation(ref culture, ref segment);
|
||||
_content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment);
|
||||
|
||||
var value = GetSourceValue(culture, segment);
|
||||
var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source);
|
||||
@@ -194,7 +194,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
public override object GetSourceValue(string culture = null, string segment = null)
|
||||
{
|
||||
ContextualizeVariation(ref culture, ref segment);
|
||||
_content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment);
|
||||
|
||||
if (culture == "" && segment == "")
|
||||
return _sourceValue;
|
||||
@@ -206,19 +206,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
}
|
||||
|
||||
private void ContextualizeVariation(ref string culture, ref string segment)
|
||||
{
|
||||
if (culture != null && segment != null) return;
|
||||
|
||||
// use context values
|
||||
var publishedVariationContext = _content.VariationContextAccessor?.VariationContext;
|
||||
if (culture == null) culture = _variations.VariesByCulture() ? publishedVariationContext?.Culture : "";
|
||||
if (segment == null) segment = _variations.VariesBySegment() ? publishedVariationContext?.Segment : "";
|
||||
}
|
||||
|
||||
public override object GetValue(string culture = null, string segment = null)
|
||||
{
|
||||
ContextualizeVariation(ref culture, ref segment);
|
||||
_content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment);
|
||||
|
||||
object value;
|
||||
lock (_locko)
|
||||
@@ -239,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
public override object GetXPathValue(string culture = null, string segment = null)
|
||||
{
|
||||
ContextualizeVariation(ref culture, ref segment);
|
||||
_content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment);
|
||||
|
||||
lock (_locko)
|
||||
{
|
||||
|
||||
@@ -186,30 +186,30 @@ namespace Umbraco.Web
|
||||
#region Value
|
||||
|
||||
/// <summary>
|
||||
/// Recursively the value of a content's property identified by its alias, if it exists, otherwise a default value.
|
||||
/// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value.
|
||||
/// </summary>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The variation language.</param>
|
||||
/// <param name="segment">The variation segment.</param>
|
||||
/// <param name="recurse">A value indicating whether to recurse.</param>
|
||||
/// <param name="fallback">Optional fallback strategy.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Recursively means: walking up the tree from <paramref name="content"/>, get the first value that can be found.</para>
|
||||
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
|
||||
/// <para>If no property with the specified alias exists, or if the property has no value, returns <paramref name="defaultValue"/>.</para>
|
||||
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
|
||||
/// <para>The alias is case-insensitive.</para>
|
||||
/// </remarks>
|
||||
public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, object defaultValue = default, bool recurse = false)
|
||||
public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default)
|
||||
{
|
||||
var property = content.GetProperty(alias);
|
||||
|
||||
// if we have a property, and it has a value, return that value
|
||||
if (property != null && property.HasValue(culture, segment))
|
||||
return property.GetValue(culture, segment);
|
||||
|
||||
return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue, recurse);
|
||||
// else let fallback try to get a value
|
||||
if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value))
|
||||
return value;
|
||||
|
||||
// else... if we have a property, at least let the converter return its own
|
||||
// vision of 'no value' (could be an empty enumerable) - otherwise, default
|
||||
return property?.GetValue(culture, segment);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -217,35 +217,35 @@ namespace Umbraco.Web
|
||||
#region Value<T>
|
||||
|
||||
/// <summary>
|
||||
/// Recursively gets the value of a content's property identified by its alias, converted to a specified type.
|
||||
/// Gets the value of a content's property identified by its alias, converted to a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The target property type.</typeparam>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The variation language.</param>
|
||||
/// <param name="segment">The variation segment.</param>
|
||||
/// <param name="fallback">Optional fallback strategy.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <param name="recurse">A value indicating whether to recurse.</param>
|
||||
/// <returns>The value of the content's property identified by the alias, converted to the specified type.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Recursively means: walking up the tree from <paramref name="content"/>, get the first value that can be found.</para>
|
||||
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
|
||||
/// <para>If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns <c>default(T)</c>.</para>
|
||||
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
|
||||
/// <para>The alias is case-insensitive.</para>
|
||||
/// </remarks>
|
||||
public static T Value<T>(this IPublishedContent content, string alias, string culture = null, string segment = null, T defaultValue = default, bool recurse = false)
|
||||
public static T Value<T>(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default)
|
||||
{
|
||||
var property = content.GetProperty(alias);
|
||||
|
||||
// if we have a property, and it has a value, return that value
|
||||
if (property != null && property.HasValue(culture, segment))
|
||||
return property.Value<T>(culture, segment);
|
||||
|
||||
return PublishedValueFallback.GetValue<T>(content, alias, culture, segment, defaultValue, recurse);
|
||||
// else let fallback try to get a value
|
||||
if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value))
|
||||
return value;
|
||||
|
||||
// else... if we have a property, at least let the converter return its own
|
||||
// vision of 'no value' (could be an empty enumerable) - otherwise, default
|
||||
return property == null ? default : property.Value<T>(culture, segment);
|
||||
}
|
||||
|
||||
// fixme - .Value() refactoring - in progress
|
||||
public static IHtmlString Value<T>(this IPublishedContent content, string aliases, Func<T, string> format, string alt = "", bool recurse = false)
|
||||
public static IHtmlString Value<T>(this IPublishedContent content, string aliases, Func<T, string> format, string alt = "", int fallback = 0)
|
||||
{
|
||||
var aliasesA = aliases.Split(',');
|
||||
if (aliasesA.Length == 0)
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <c>IPublishedProperty</c>.
|
||||
/// </summary>
|
||||
public static class PublishedPropertyExtension
|
||||
{
|
||||
// see notes in PublishedElementExtensions
|
||||
//
|
||||
private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback;
|
||||
|
||||
#region Value
|
||||
|
||||
public static object Value(this IPublishedProperty property, string culture = null, string segment = null, object defaultValue = default)
|
||||
{
|
||||
if (property.HasValue(culture, segment))
|
||||
return property.GetValue(culture, segment);
|
||||
|
||||
return PublishedValueFallback.GetValue(property, culture, segment, defaultValue);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Value<T>
|
||||
|
||||
public static T Value<T>(this IPublishedProperty property, string culture = null, string segment = null, T defaultValue = default)
|
||||
{
|
||||
// for Value<T> when defaultValue is not specified, and HasValue() is false, we still want to convert the result (see below)
|
||||
// but we have no way to tell whether default value is specified or not - we could do it with overloads, but then defaultValue
|
||||
// comes right after property and conflicts with culture when T is string - so we're just not doing it - if defaultValue is
|
||||
// default, whether specified or not, we give a chance to the converter
|
||||
//
|
||||
//if (!property.HasValue(culture, segment) && 'defaultValue is explicitely specified') return defaultValue;
|
||||
|
||||
// give the converter a chance to handle the default value differently
|
||||
// eg for IEnumerable<T> it may return Enumerable<T>.Empty instead of null
|
||||
|
||||
var value = property.GetValue(culture, segment);
|
||||
|
||||
// if value is null (strange but why not) it still is OK to call TryConvertTo
|
||||
// because it's an extension method (hence no NullRef) which will return a
|
||||
// failed attempt. So, no need to care for value being null here.
|
||||
|
||||
// if already the requested type, return
|
||||
if (value is T variable) return variable;
|
||||
|
||||
// if can convert to requested type, return
|
||||
var convert = value.TryConvertTo<T>();
|
||||
if (convert.Success) return convert.Result;
|
||||
|
||||
// at that point, the code tried with the raw value
|
||||
// that makes no sense because it sort of is unpredictable,
|
||||
// you wouldn't know when the converters run or don't run.
|
||||
// so, it's commented out now.
|
||||
|
||||
// try with the raw value
|
||||
//var source = property.ValueSource;
|
||||
//if (source is string) source = TextValueConverterHelper.ParseStringValueSource((string)source);
|
||||
//if (source is T) return (T)source;
|
||||
//convert = source.TryConvertTo<T>();
|
||||
//if (convert.Success) return convert.Result;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
@@ -98,6 +99,7 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The variation language.</param>
|
||||
/// <param name="segment">The variation segment.</param>
|
||||
/// <param name="fallback">Optional fallback strategy.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
|
||||
/// <remarks>
|
||||
@@ -106,14 +108,21 @@ namespace Umbraco.Web
|
||||
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
|
||||
/// <para>The alias is case-insensitive.</para>
|
||||
/// </remarks>
|
||||
public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null, object defaultValue = default)
|
||||
public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default)
|
||||
{
|
||||
var property = content.GetProperty(alias);
|
||||
|
||||
// if we have a property, and it has a value, return that value
|
||||
if (property != null && property.HasValue(culture, segment))
|
||||
return property.GetValue(culture, segment);
|
||||
|
||||
return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue);
|
||||
// else let fallback try to get a value
|
||||
if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value))
|
||||
return value;
|
||||
|
||||
// else... if we have a property, at least let the converter return its own
|
||||
// vision of 'no value' (could be an empty enumerable) - otherwise, default
|
||||
return property?.GetValue(culture, segment);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -128,6 +137,7 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The variation language.</param>
|
||||
/// <param name="segment">The variation segment.</param>
|
||||
/// <param name="fallback">Optional fallback strategy.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>The value of the content's property identified by the alias, converted to the specified type.</returns>
|
||||
/// <remarks>
|
||||
@@ -136,14 +146,21 @@ namespace Umbraco.Web
|
||||
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
|
||||
/// <para>The alias is case-insensitive.</para>
|
||||
/// </remarks>
|
||||
public static T Value<T>(this IPublishedElement content, string alias, string culture = null, string segment = null, T defaultValue = default)
|
||||
public static T Value<T>(this IPublishedElement content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default)
|
||||
{
|
||||
var property = content.GetProperty(alias);
|
||||
|
||||
// if we have a property, and it has a value, return that value
|
||||
if (property != null && property.HasValue(culture, segment))
|
||||
return property.Value<T>(culture, segment);
|
||||
|
||||
return PublishedValueFallback.GetValue<T>(content, alias, culture, segment, defaultValue);
|
||||
// else let fallback try to get a value
|
||||
if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value))
|
||||
return value;
|
||||
|
||||
// else... if we have a property, at least let the converter return its own
|
||||
// vision of 'no value' (could be an empty enumerable) - otherwise, default
|
||||
return property == null ? default : property.Value<T>(culture, segment);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <c>IPublishedProperty</c>.
|
||||
/// </summary>
|
||||
public static class PublishedPropertyExtension
|
||||
{
|
||||
// see notes in PublishedElementExtensions
|
||||
//
|
||||
private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback;
|
||||
|
||||
#region Value
|
||||
|
||||
public static object Value(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default)
|
||||
{
|
||||
if (property.HasValue(culture, segment))
|
||||
return property.GetValue(culture, segment);
|
||||
|
||||
return PublishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var value)
|
||||
? value
|
||||
: property.GetValue(culture, segment); // give converter a chance to return it's own vision of "no value"
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Value<T>
|
||||
|
||||
public static T Value<T>(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default)
|
||||
{
|
||||
if (property.HasValue(culture, segment))
|
||||
{
|
||||
// we have a value
|
||||
// try to cast or convert it
|
||||
var value = property.GetValue(culture, segment);
|
||||
if (value is T valueAsT) return valueAsT;
|
||||
var valueConverted = value.TryConvertTo<T>();
|
||||
if (valueConverted) return valueConverted.Result;
|
||||
|
||||
// cannot cast nor convert the value, nothing we can return but 'default'
|
||||
// note: we don't want to fallback in that case - would make little sense
|
||||
return default;
|
||||
}
|
||||
|
||||
// we don't have a value, try fallback
|
||||
if (PublishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue))
|
||||
return fallbackValue;
|
||||
|
||||
// we don't have a value - neither direct nor fallback
|
||||
// give a chance to the converter to return something (eg empty enumerable)
|
||||
var noValue = property.GetValue(culture, segment);
|
||||
if (noValue is T noValueAsT) return noValueAsT;
|
||||
var noValueConverted = noValue.TryConvertTo<T>();
|
||||
if (noValueConverted) return noValueConverted.Result;
|
||||
|
||||
// cannot cast noValue nor convert it, nothing we can return but 'default'
|
||||
return default;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -190,8 +190,16 @@ namespace Umbraco.Web.Routing
|
||||
|
||||
// this the ONLY place where we deal with default culture - IUrlProvider always receive a culture
|
||||
// be nice with tests, assume things can be null, ultimately fall back to invariant
|
||||
if (culture == null)
|
||||
culture = _variationContextAccessor?.VariationContext?.Culture ?? "";
|
||||
// (but only for variant content of course)
|
||||
if (content.ContentType.VariesByCulture())
|
||||
{
|
||||
if (culture == null)
|
||||
culture = _variationContextAccessor?.VariationContext?.Culture ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
culture = null;
|
||||
}
|
||||
|
||||
if (current == null)
|
||||
current = _umbracoContext.CleanedUmbracoUrl;
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
//using System;
|
||||
//using System.Linq;
|
||||
//using System.Net.Http.Formatting;
|
||||
//using Umbraco.Web.Models.Trees;
|
||||
//using Umbraco.Web.Mvc;
|
||||
//using Umbraco.Web.WebApi.Filters;
|
||||
//using umbraco;
|
||||
//using umbraco.cms.businesslogic.packager;
|
||||
//using Umbraco.Core.Services;
|
||||
//using Umbraco.Web._Legacy.Actions;
|
||||
//using Constants = Umbraco.Core.Constants;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using umbraco;
|
||||
using umbraco.cms.businesslogic.packager;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web._Legacy.Actions;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
//namespace Umbraco.Web.Trees
|
||||
//{
|
||||
// [UmbracoTreeAuthorize(Constants.Trees.Packages)]
|
||||
// [Tree(Constants.Applications.Developer, Constants.Trees.Packages, null, sortOrder: 0)]
|
||||
// [PluginController("UmbracoTrees")]
|
||||
// [CoreTree]
|
||||
// public class PackagesTreeController : TreeController
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Helper method to create a root model for a tree
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
// {
|
||||
// var root = base.CreateRootNode(queryStrings);
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Packages)]
|
||||
[Tree(Constants.Applications.Developer, Constants.Trees.Packages, null, sortOrder: 0)]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class PackagesTreeController : TreeController
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper method to create a root model for a tree
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
{
|
||||
var root = base.CreateRootNode(queryStrings);
|
||||
|
||||
root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Developer, Constants.Trees.Packages, "overview");
|
||||
|
||||
root.Icon = "icon-box";
|
||||
|
||||
// //this will load in a custom UI instead of the dashboard for the root node
|
||||
// root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Developer, Constants.Trees.Packages, "overview");
|
||||
// root.Icon = "icon-box";
|
||||
return root;
|
||||
}
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var nodes = new TreeNodeCollection();
|
||||
|
||||
// return root;
|
||||
// }
|
||||
// protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
// {
|
||||
// var nodes = new TreeNodeCollection();
|
||||
var createdPackages = CreatedPackage.GetAllCreatedPackages();
|
||||
|
||||
// var createdPackages = CreatedPackage.GetAllCreatedPackages();
|
||||
|
||||
// if (id == "created")
|
||||
// {
|
||||
// nodes.AddRange(
|
||||
// createdPackages
|
||||
// .OrderBy(entity => entity.Data.Name)
|
||||
// .Select(dt =>
|
||||
// {
|
||||
// var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false,
|
||||
// string.Format("/{0}/framed/{1}",
|
||||
// queryStrings.GetValue<string>("application"),
|
||||
// Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id)));
|
||||
// return node;
|
||||
// }));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //must be root
|
||||
// var node = CreateTreeNode(
|
||||
// "created",
|
||||
// id,
|
||||
// queryStrings,
|
||||
// Services.TextService.Localize("treeHeaders/createdPackages"),
|
||||
// "icon-folder",
|
||||
// createdPackages.Count > 0,
|
||||
// string.Empty);
|
||||
if (id == "created")
|
||||
{
|
||||
nodes.AddRange(
|
||||
createdPackages
|
||||
.OrderBy(entity => entity.Data.Name)
|
||||
.Select(dt =>
|
||||
{
|
||||
var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false,
|
||||
string.Format("/{0}/framed/{1}",
|
||||
queryStrings.GetValue<string>("application"),
|
||||
Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id)));
|
||||
return node;
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
//must be root
|
||||
var node = CreateTreeNode(
|
||||
"created",
|
||||
id,
|
||||
queryStrings,
|
||||
Services.TextService.Localize("treeHeaders/createdPackages"),
|
||||
"icon-folder",
|
||||
createdPackages.Count > 0,
|
||||
string.Empty);
|
||||
|
||||
|
||||
|
||||
// //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now.
|
||||
// node.AdditionalData["jsClickCallback"] = "javascript:void(0);";
|
||||
//TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now.
|
||||
node.AdditionalData["jsClickCallback"] = "javascript:void(0);";
|
||||
|
||||
// nodes.Add(node);
|
||||
// }
|
||||
nodes.Add(node);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// return nodes;
|
||||
// }
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
||||
// {
|
||||
// var menu = new MenuItemCollection();
|
||||
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
// // Root actions
|
||||
// if (id == "-1")
|
||||
// {
|
||||
// menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)))
|
||||
// .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue<string>("application"));
|
||||
// }
|
||||
// else if (id == "created")
|
||||
// {
|
||||
// menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)))
|
||||
// .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue<string>("application"));
|
||||
// Root actions
|
||||
if (id == "-1")
|
||||
{
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)))
|
||||
.ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue<string>("application"));
|
||||
}
|
||||
else if (id == "created")
|
||||
{
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)))
|
||||
.ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue<string>("application"));
|
||||
|
||||
// menu.Items.Add<RefreshNode, ActionRefresh>(
|
||||
// Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //it's a package node
|
||||
// menu.Items.Add<ActionDelete>(Services.TextService.Localize("actions", ActionDelete.Instance.Alias));
|
||||
// }
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(
|
||||
Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//it's a package node
|
||||
menu.Items.Add<ActionDelete>(Services.TextService.Localize("actions", ActionDelete.Instance.Alias));
|
||||
}
|
||||
|
||||
// return menu;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,7 +906,7 @@
|
||||
<Compile Include="Models\LoginStatusModel.cs" />
|
||||
<Compile Include="PropertyEditors\ValueConverters\MarkdownEditorValueConverter.cs" />
|
||||
<Compile Include="PropertyEditors\ValueConverters\TextStringValueConverter.cs" />
|
||||
<Compile Include="PublishedContentPropertyExtension.cs" />
|
||||
<Compile Include="PublishedPropertyExtension.cs" />
|
||||
<Compile Include="Models\UmbracoProperty.cs" />
|
||||
<Compile Include="Mvc\MergeParentContextViewDataAttribute.cs" />
|
||||
<Compile Include="Mvc\ViewDataDictionaryExtensions.cs" />
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace umbraco
|
||||
//check for published content and get its value using that
|
||||
if (publishedContent != null && (publishedContent.HasProperty(_fieldName) || recursive))
|
||||
{
|
||||
var pval = publishedContent.Value(_fieldName, recurse: recursive);
|
||||
var pval = publishedContent.Value(_fieldName, fallback: Fallback.ToAncestors);
|
||||
var rval = pval == null ? string.Empty : pval.ToString();
|
||||
_fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ namespace umbraco
|
||||
{
|
||||
if (publishedContent != null && (publishedContent.HasProperty(altFieldName) || recursive))
|
||||
{
|
||||
var pval = publishedContent.Value(altFieldName, recurse: recursive);
|
||||
var pval = publishedContent.Value(altFieldName, fallback: Fallback.ToAncestors);
|
||||
var rval = pval == null ? string.Empty : pval.ToString();
|
||||
_fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user