Merge branch 'temp8-I3089' of https://github.com/umbraco/Umbraco-CMS into temp8-I3089
This commit is contained in:
+2
-1
@@ -48,4 +48,5 @@ Umbraco is contribution focused and community driven. If you want to contribute
|
||||
|
||||
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](CONTRIBUTING_DETAILED.md#reporting-bugs).
|
||||
|
||||
To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org).
|
||||
You can comment and report issues on the [github issue tracker](https://github.com/umbraco/Umbraco-CMS/issues).
|
||||
Since [September 2018](https://umbraco.com/blog/a-second-take-on-umbraco-issue-tracker-hello-github-issues/) the old issue tracker is in read only mode, but can still be found at [http://issues.umbraco.org](http://issues.umbraco.org).
|
||||
|
||||
@@ -21,6 +21,6 @@ The following items will now be automatically included when creating a deploy pa
|
||||
system: umbraco, config\splashes and global.asax.
|
||||
|
||||
Please read the release notes on our.umbraco.com:
|
||||
http://our.umbraco.com/contribute/releases
|
||||
https://our.umbraco.com/download/releases
|
||||
|
||||
- Umbraco
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
[ConfigurationProperty("loginBackgroundImage")]
|
||||
internal InnerTextConfigurationElement<string> LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty);
|
||||
[ConfigurationProperty("StripUdiAttributes")]
|
||||
internal InnerTextConfigurationElement<bool> StripUdiAttributes
|
||||
{
|
||||
get { return GetOptionalTextElement("StripUdiAttributes", true); }
|
||||
}
|
||||
|
||||
|
||||
string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress;
|
||||
|
||||
@@ -136,6 +142,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes;
|
||||
|
||||
bool IContentSection.StripUdiAttributes => StripUdiAttributes;
|
||||
|
||||
string IContentSection.LoginBackgroundImage => LoginBackgroundImage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
IEnumerable<string> ImageTagAllowedAttributes { get; }
|
||||
|
||||
IEnumerable<IImagingAutoFillUploadField> ImageAutoFillProperties { get; }
|
||||
|
||||
|
||||
string ScriptFolderPath { get; }
|
||||
|
||||
IEnumerable<string> ScriptFileTypes { get; }
|
||||
@@ -66,5 +66,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
bool EnableInheritedMediaTypes { get; }
|
||||
|
||||
string LoginBackgroundImage { get; }
|
||||
bool StripUdiAttributes { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,11 @@ namespace Umbraco.Core
|
||||
/// Property alias for the Media's file extension.
|
||||
/// </summary>
|
||||
public const string Extension = "umbracoExtension";
|
||||
|
||||
/// <summary>
|
||||
/// The default height/width of an image file if the size can't be determined from the metadata
|
||||
/// </summary>
|
||||
public const int DefaultSize = 200;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
/// </remarks>
|
||||
public const string RecycleBinMediaPathPrefix = "-1,-21,";
|
||||
|
||||
public const int DefaultLabelDataTypeId = -92;
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
public const string UmbracoUpgradePlanName = "Umbraco.Core";
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ namespace Umbraco.Core.Events
|
||||
|
||||
// fixme see notes above
|
||||
// delete event args does NOT superceedes 'unpublished' event
|
||||
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished")
|
||||
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "Unpublished")
|
||||
return false;
|
||||
|
||||
// found occurences, need to determine if this event args is superceded
|
||||
|
||||
@@ -230,7 +230,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);
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
/// <summary>
|
||||
/// Used when nodes are unpublished
|
||||
/// </summary>
|
||||
UnPublish,
|
||||
Unpublish,
|
||||
/// <summary>
|
||||
/// Used when nodes are moved
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of tag changes.
|
||||
/// </summary>
|
||||
internal class PropertyTagChange
|
||||
{
|
||||
public ChangeType Type { get; set; }
|
||||
|
||||
public IEnumerable<(string Type, string Tags)> Tags { get; set; }
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
Replace,
|
||||
Remove,
|
||||
Merge
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -748,7 +748,7 @@ ORDER BY colName";
|
||||
|
||||
if (excludeUserGroups != null && excludeUserGroups.Length > 0)
|
||||
{
|
||||
var subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id
|
||||
const string subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id
|
||||
FROM umbracoUser
|
||||
INNER JOIN umbracoUser2UserGroup ON umbracoUser2UserGroup.userId = umbracoUser.id
|
||||
INNER JOIN umbracoUserGroup ON umbracoUserGroup.id = umbracoUser2UserGroup.userGroupId
|
||||
@@ -809,7 +809,7 @@ ORDER BY colName";
|
||||
sql = new SqlTranslator<IUser>(sql, query).Translate();
|
||||
|
||||
// get sorted and filtered sql
|
||||
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql), orderDirection, orderBy);
|
||||
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderDirection, orderBy);
|
||||
|
||||
// get a page of results and total count
|
||||
var pagedResult = Database.Page<UserDto>(pageIndex + 1, pageSize, sqlNodeIdsWithSort);
|
||||
@@ -820,11 +820,17 @@ ORDER BY colName";
|
||||
return pagedResult.Items.Select(UserFactory.BuildEntity);
|
||||
}
|
||||
|
||||
private Sql<ISqlContext> ApplyFilter(Sql<ISqlContext> sql, Sql<ISqlContext> filterSql)
|
||||
private Sql<ISqlContext> ApplyFilter(Sql<ISqlContext> sql, Sql<ISqlContext> filterSql, bool hasWhereClause)
|
||||
{
|
||||
if (filterSql == null) return sql;
|
||||
|
||||
sql.Append(SqlContext.Sql(" WHERE " + filterSql.SQL.TrimStart("AND "), filterSql.Arguments));
|
||||
//ensure we don't append a WHERE if there is already one
|
||||
var args = filterSql.Arguments;
|
||||
var sqlFilter = hasWhereClause
|
||||
? filterSql.SQL
|
||||
: " WHERE " + filterSql.SQL.TrimStart("AND ");
|
||||
|
||||
sql.Append(SqlContext.Sql(sqlFilter, args));
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
@@ -63,5 +65,6 @@ namespace Umbraco.Core.Security
|
||||
/// Used so that we aren't creating a new CultureInfo object for every single request
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, CultureInfo> UserCultures = new ConcurrentDictionary<string, CultureInfo>();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,7 +635,9 @@ namespace Umbraco.Core.Security
|
||||
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime();
|
||||
//if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
|
||||
var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
|
||||
user.LastLoginDate = dt;
|
||||
}
|
||||
if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc")
|
||||
|| (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|
||||
|
||||
@@ -988,14 +988,14 @@ namespace Umbraco.Core.Services.Implement
|
||||
UnpublishResultType result;
|
||||
if (culture == "*" || culture == null)
|
||||
{
|
||||
Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id);
|
||||
Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id);
|
||||
result = UnpublishResultType.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
Audit(AuditType.UnPublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id);
|
||||
Audit(AuditType.Unpublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id);
|
||||
if (!content.Published)
|
||||
Audit(AuditType.UnPublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id);
|
||||
Audit(AuditType.Unpublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id);
|
||||
result = content.Published ? UnpublishResultType.SuccessCulture : UnpublishResultType.SuccessMandatoryCulture;
|
||||
}
|
||||
scope.Complete();
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1120,9 +1120,9 @@ namespace Umbraco.Core.Services.Implement
|
||||
if (unpublishResult.Success) // and succeeded, trigger events
|
||||
{
|
||||
// events and audit
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), "UnPublished");
|
||||
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), "Unpublished");
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshBranch).ToEventArgs());
|
||||
Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id);
|
||||
Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id);
|
||||
scope.Complete();
|
||||
return new PublishResult(PublishResultType.Success, evtMsgs, content);
|
||||
}
|
||||
@@ -1348,10 +1348,10 @@ namespace Umbraco.Core.Services.Implement
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
// if it's not trashed yet, and published, we should unpublish
|
||||
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// but... Unpublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// just raise the event
|
||||
if (content.Trashed == false && content.Published)
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(UnPublished));
|
||||
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(Unpublished));
|
||||
|
||||
DeleteLocked(scope, content);
|
||||
|
||||
@@ -2098,12 +2098,12 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <summary>
|
||||
/// Occurs before unpublish
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> UnPublishing;
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Unpublishing;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after unpublish
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> UnPublished;
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Unpublished;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after change.
|
||||
@@ -2197,8 +2197,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
// ensures that a document can be unpublished
|
||||
internal UnpublishResult StrategyCanUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs)
|
||||
{
|
||||
// raise UnPublishing event
|
||||
if (scope.Events.DispatchCancelable(UnPublishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
|
||||
// raise Unpublishing event
|
||||
if (scope.Events.DispatchCancelable(Unpublishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
|
||||
{
|
||||
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id);
|
||||
return new UnpublishResult(UnpublishResultType.FailedCancelledByEvent, evtMsgs, content);
|
||||
@@ -2282,10 +2282,10 @@ namespace Umbraco.Core.Services.Implement
|
||||
foreach (var content in contents.OrderByDescending(x => x.ParentId))
|
||||
{
|
||||
// if it's not trashed yet, and published, we should unpublish
|
||||
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// but... Unpublishing event makes no sense (not going to cancel?) and no need to save
|
||||
// just raise the event
|
||||
if (content.Trashed == false && content.Published)
|
||||
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(UnPublished));
|
||||
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(Unpublished));
|
||||
|
||||
// if current content has children, move them to trash
|
||||
var c = content;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
private readonly IContentTypeRepository _contentTypeRepository;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private static HttpClient _httpClient;
|
||||
|
||||
public PackagingService(
|
||||
ILogger logger,
|
||||
@@ -1441,7 +1442,6 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns></returns>
|
||||
public string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId)
|
||||
{
|
||||
using (var httpClient = new HttpClient())
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
//includeHidden = true because we don't care if it's hidden we want to get the file regardless
|
||||
@@ -1449,7 +1449,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
byte[] bytes;
|
||||
try
|
||||
{
|
||||
bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||
if (_httpClient == null)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
bytes = _httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
|
||||
@@ -76,11 +76,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
// reload - cheap, cached
|
||||
|
||||
// default role is single server, but if registrations contain more
|
||||
// than one active server, then role is master or slave
|
||||
// than one active server, then role is master or replica
|
||||
regs = _serverRegistrationRepository.GetMany().ToArray();
|
||||
|
||||
// default role is single server, but if registrations contain more
|
||||
// than one active server, then role is master or slave
|
||||
// than one active server, then role is master or replica
|
||||
_currentServerRole = regs.Count(x => x.IsActive) > 1
|
||||
? (server.IsMaster ? ServerRole.Master : ServerRole.Replica)
|
||||
: ServerRole.Single;
|
||||
|
||||
@@ -1384,6 +1384,30 @@ namespace Umbraco.Core
|
||||
return idCheckList.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a file name to a friendly name for a content item
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToFriendlyName(this string fileName)
|
||||
{
|
||||
// strip the file extension
|
||||
fileName = fileName.StripFileExtension();
|
||||
|
||||
// underscores and dashes to spaces
|
||||
fileName = fileName.ReplaceMany(new[] { '_', '-' }, ' ');
|
||||
|
||||
// any other conversions ?
|
||||
|
||||
// Pascalcase (to be done last)
|
||||
fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName);
|
||||
|
||||
// Replace multiple consecutive spaces with a single space
|
||||
fileName = string.Join(" ", fileName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// From: http://stackoverflow.com/a/961504/5018
|
||||
// filters control characters but allows only properly-formed surrogate sequences
|
||||
private static readonly Lazy<Regex> InvalidXmlChars = new Lazy<Regex>(() =>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
Single = 1,
|
||||
|
||||
/// <summary>
|
||||
/// In a multi-servers environment, the server is a slave server.
|
||||
/// In a multi-servers environment, the server is a replica server.
|
||||
/// </summary>
|
||||
Replica = 2,
|
||||
|
||||
|
||||
@@ -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" />
|
||||
@@ -727,7 +730,6 @@
|
||||
<Compile Include="Models\PropertyCollection.cs" />
|
||||
<Compile Include="Models\PropertyGroup.cs" />
|
||||
<Compile Include="Models\PropertyGroupCollection.cs" />
|
||||
<Compile Include="Models\PropertyTagChange.cs" />
|
||||
<Compile Include="Models\PropertyType.cs" />
|
||||
<Compile Include="Models\PropertyTypeCollection.cs" />
|
||||
<Compile Include="Models\PublicAccessEntry.cs" />
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace Umbraco.Tests.Cache
|
||||
new EventDefinition<IContentService, MoveEventArgs<IContent>>(null, serviceContext.ContentService, new MoveEventArgs<IContent>(new MoveEventInfo<IContent>(null, "", -1)), "Trashed"),
|
||||
new EventDefinition<IContentService, RecycleBinEventArgs>(null, serviceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid())),
|
||||
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, serviceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "Published"),
|
||||
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, serviceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "UnPublished"),
|
||||
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, serviceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "Unpublished"),
|
||||
|
||||
new EventDefinition<IPublicAccessService, SaveEventArgs<PublicAccessEntry>>(null, serviceContext.PublicAccessService, new SaveEventArgs<PublicAccessEntry>(Enumerable.Empty<PublicAccessEntry>())),
|
||||
new EventDefinition<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>>(null, serviceContext.PublicAccessService, new DeleteEventArgs<PublicAccessEntry>(Enumerable.Empty<PublicAccessEntry>())),
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -463,7 +463,7 @@ namespace Umbraco.Tests.Integration
|
||||
|
||||
#endregion
|
||||
|
||||
#region Save, Publish & UnPublish single content
|
||||
#region Save, Publish & Unpublish single content
|
||||
|
||||
[Test]
|
||||
public void SaveUnpublishedContent()
|
||||
@@ -721,7 +721,7 @@ namespace Umbraco.Tests.Integration
|
||||
|
||||
#endregion
|
||||
|
||||
#region Publish & UnPublish branch
|
||||
#region Publish & Unpublish branch
|
||||
|
||||
[Test]
|
||||
public void UnpublishContentBranch()
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
|
||||
namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class AuditRepositoryTest : TestWithDatabaseBase
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void Can_Add_Audit_Entry()
|
||||
{
|
||||
@@ -23,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
using (var scope = sp.CreateScope())
|
||||
{
|
||||
var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger);
|
||||
repo.Save(new AuditItem(-1, "This is a System audit trail", AuditType.System, 0));
|
||||
repo.Save(new AuditItem(-1, "This is a System audit trail", AuditType.System, -1));
|
||||
|
||||
var dtos = scope.Database.Fetch<LogDto>("WHERE id > -1");
|
||||
|
||||
@@ -42,8 +46,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, 0));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, 0));
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
@@ -60,6 +64,49 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items_By_User_Id_With_Query_And_Filter()
|
||||
{
|
||||
var sp = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = sp.CreateScope())
|
||||
{
|
||||
var repo = new AuditRepository((IScopeAccessor)sp, CacheHelper, Logger);
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
using (var scope = sp.CreateScope())
|
||||
{
|
||||
var repo = new AuditRepository((IScopeAccessor)sp, CacheHelper, Logger);
|
||||
|
||||
var query = sp.SqlContext.Query<IAuditItem>().Where(x => x.UserId == -1);
|
||||
|
||||
try
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
|
||||
|
||||
var page = repo.GetPagedResultsByQuery(query, 0, 10, out var total, Direction.Descending,
|
||||
new[] { AuditType.Publish },
|
||||
sp.SqlContext.Query<IAuditItem>().Where(x => x.UserId > -2));
|
||||
|
||||
Assert.AreEqual(10, page.Count());
|
||||
Assert.AreEqual(100, total);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items_With_AuditType_Filter()
|
||||
{
|
||||
@@ -70,8 +117,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, 0));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, 0));
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
@@ -101,8 +148,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
repo.Save(new AuditItem(i, "Content created", AuditType.New, 0));
|
||||
repo.Save(new AuditItem(i, "Content published", AuditType.Publish, 0));
|
||||
repo.Save(new AuditItem(i, "Content created", AuditType.New, -1));
|
||||
repo.Save(new AuditItem(i, "Content published", AuditType.Publish, -1));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -8,17 +8,19 @@ using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class UserRepositoryTest : TestWithDatabaseBase
|
||||
{
|
||||
private MediaRepository CreateMediaRepository(IScopeProvider provider, out IMediaTypeRepository mediaTypeRepository)
|
||||
@@ -50,14 +52,14 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
private UserRepository CreateRepository(IScopeProvider provider)
|
||||
{
|
||||
var accessor = (IScopeAccessor) provider;
|
||||
var repository = new UserRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Mock.Of<ILogger>(), Mock.Of<IMapperCollection>(), TestObjects.GetGlobalSettings());
|
||||
var repository = new UserRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Logger, Mappers, TestObjects.GetGlobalSettings());
|
||||
return repository;
|
||||
}
|
||||
|
||||
private UserGroupRepository CreateUserGroupRepository(IScopeProvider provider)
|
||||
{
|
||||
var accessor = (IScopeAccessor) provider;
|
||||
return new UserGroupRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Mock.Of<ILogger>());
|
||||
return new UserGroupRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Logger);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -338,7 +340,72 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var result = repository.Count(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.GreaterThanOrEqualTo(2));
|
||||
Assert.AreEqual(2, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Paged_Results_By_Query_And_Filter_And_Groups()
|
||||
{
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
var query = provider.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2");
|
||||
|
||||
try
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
|
||||
|
||||
// Act
|
||||
var result = repository.GetPagedResultsByQuery(query, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
|
||||
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
|
||||
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id > -1));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, totalRecs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Paged_Results_With_Filter_And_Groups()
|
||||
{
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
|
||||
try
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
|
||||
|
||||
// Act
|
||||
var result = repository.GetPagedResultsByQuery(null, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
|
||||
includeUserGroups: new[] { Constants.Security.AdminGroupAlias, Constants.Security.SensitiveDataGroupAlias },
|
||||
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
|
||||
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id == -1));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, totalRecs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Umbraco.Tests.Scoping
|
||||
[Test]
|
||||
public void SupersededEvents2()
|
||||
{
|
||||
Test_UnPublished += OnDoThingFail;
|
||||
Test_Unpublished += OnDoThingFail;
|
||||
Test_Deleted += OnDoThingFail;
|
||||
|
||||
var contentService = Mock.Of<IContentService>();
|
||||
@@ -178,7 +178,7 @@ namespace Umbraco.Tests.Scoping
|
||||
var scopeProvider = _testObjects.GetScopeProvider(Mock.Of<ILogger>());
|
||||
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
|
||||
{
|
||||
scope.Events.Dispatch(Test_UnPublished, contentService, new PublishEventArgs<IContent>(new [] { content }), "UnPublished");
|
||||
scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs<IContent>(new [] { content }), "Unpublished");
|
||||
scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs<IContent>(new [] { content }), "Deleted");
|
||||
|
||||
// see U4-10764
|
||||
@@ -395,7 +395,7 @@ namespace Umbraco.Tests.Scoping
|
||||
|
||||
public static event TypedEventHandler<ScopeEventDispatcherTests, SaveEventArgs<decimal>> DoThing3;
|
||||
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Test_UnPublished;
|
||||
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Test_Unpublished;
|
||||
public static event TypedEventHandler<IContentService, DeleteEventArgs<IContent>> Test_Deleted;
|
||||
|
||||
public class TestEventArgs : CancellableObjectEventArgs
|
||||
|
||||
@@ -1208,7 +1208,7 @@ namespace Umbraco.Tests.Services
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_UnPublish_Content()
|
||||
public void Can_Unpublish_Content()
|
||||
{
|
||||
// Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
@@ -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]
|
||||
|
||||
@@ -3,9 +3,11 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
@@ -30,6 +32,14 @@ namespace Umbraco.Tests.Strings
|
||||
Assert.IsInstanceOf<MockShortStringHelper>(helper);
|
||||
}
|
||||
|
||||
[TestCase("hello-world.png", "Hello World")]
|
||||
[TestCase("hello-world .png", "Hello World")]
|
||||
[TestCase("_hello-world __1.png", "Hello World 1")]
|
||||
public void To_Friendly_Name(string first, string second)
|
||||
{
|
||||
Assert.AreEqual(first.ToFriendlyName(), second);
|
||||
}
|
||||
|
||||
[TestCase("hello", "world", false)]
|
||||
[TestCase("hello", "hello", true)]
|
||||
[TestCase("hellohellohellohellohellohellohello", "hellohellohellohellohellohellohelloo", false)]
|
||||
|
||||
@@ -172,6 +172,11 @@ namespace Umbraco.Tests.Testing
|
||||
Container.RegisterSingleton<ILogger>(f => new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config"))));
|
||||
Container.RegisterSingleton<IProfiler>(f => new LogProfiler(f.GetInstance<ILogger>()));
|
||||
}
|
||||
else if (option == UmbracoTestOptions.Logger.Console)
|
||||
{
|
||||
Container.RegisterSingleton<ILogger>(f => new ConsoleLogger());
|
||||
Container.RegisterSingleton<IProfiler>(f => new LogProfiler(f.GetInstance<ILogger>()));
|
||||
}
|
||||
|
||||
Container.RegisterSingleton(f => new ProfilingLogger(f.GetInstance<ILogger>(), f.GetInstance<IProfiler>()));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
// pure mocks
|
||||
Mock,
|
||||
// Serilog for tests
|
||||
Serilog
|
||||
Serilog,
|
||||
// console logger
|
||||
Console
|
||||
}
|
||||
|
||||
public enum Database
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
<PackageReference Include="AutoMapper" Version="7.0.1" />
|
||||
<PackageReference Include="Castle.Core" Version="4.2.1" />
|
||||
<PackageReference Include="Examine" Version="1.0.0-beta025" />
|
||||
<PackageReference Include="HtmlAgilityPack">
|
||||
<Version>1.8.9</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LightInject" Version="5.1.2" />
|
||||
<PackageReference Include="LightInject.Annotation" Version="1.1.0" />
|
||||
<PackageReference Include="Lucene.Net" Version="3.0.3" />
|
||||
@@ -120,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" />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using LightInject;
|
||||
using HtmlAgilityPack;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
@@ -12,7 +12,6 @@ using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Stubs;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
@@ -21,6 +20,8 @@ using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.Templates;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Tests.Web
|
||||
{
|
||||
@@ -62,6 +63,14 @@ namespace Umbraco.Tests.Web
|
||||
[TestCase("hello href=\"{localLink:umb://document-type/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
|
||||
//this one has an invalid char so won't match
|
||||
[TestCase("hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")]
|
||||
// with a-tag with data-udi attribute, that needs to be stripped
|
||||
[TestCase("hello <a data-udi=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\"> world</a> ", "hello <a href=\"/my-test-url\"> world</a> ")]
|
||||
// with a-tag with data-udi attribute spelled wrong, so don't need stripping
|
||||
[TestCase("hello <a data-uid=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\"> world</a> ", "hello <a data-uid=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" href=\"/my-test-url\"> world</a> ")]
|
||||
// with a img-tag with data-udi id, that needs to be strippde
|
||||
[TestCase("hello <img data-udi=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" src=\"imageofcats.jpg\"> world ", "hello <img src=\"imageofcats.jpg\"> world ")]
|
||||
// with a img-tag with data-udi id spelled wrong, so don't need stripping
|
||||
[TestCase("hello <img data-uid=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" src=\"imageofcats.jpg\"> world ", "hello <img data-uid=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" src=\"imageofcats.jpg\"> world ")]
|
||||
public void ParseLocalLinks(string input, string result)
|
||||
{
|
||||
var serviceCtxMock = new TestObjects(null).GetServiceContextMock();
|
||||
@@ -102,7 +111,7 @@ namespace Umbraco.Tests.Web
|
||||
//setup a quick mock of the WebRouting section
|
||||
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "AutoLegacy")),
|
||||
//pass in the custom url provider
|
||||
new[]{ testUrlProvider.Object },
|
||||
new[] { testUrlProvider.Object },
|
||||
globalSettings,
|
||||
new TestVariationContextAccessor(),
|
||||
true))
|
||||
@@ -112,5 +121,27 @@ namespace Umbraco.Tests.Web
|
||||
Assert.AreEqual(result, output);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StripDataUdiAttributesUsingSrtringOnLinks()
|
||||
{
|
||||
var input = "hello <a data-udi=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" href=\"/my-test-url\"> world</a> ";
|
||||
var expected = "hello <a href=\"/my-test-url\"> world</a> ";
|
||||
|
||||
var result = TemplateUtilities.StripUdiDataAttributes(input);
|
||||
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StripDataUdiAttributesUsingStringOnImages()
|
||||
{
|
||||
var input = "hello <img data-udi=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" src=\"imageofcats.jpg\"> world ";
|
||||
var expected = "hello <img src=\"imageofcats.jpg\"> world ";
|
||||
|
||||
var result = TemplateUtilities.StripUdiDataAttributes(input);
|
||||
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
+6012
-3181
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,12 @@
|
||||
{
|
||||
"author": "Umbraco HQ",
|
||||
"name": "umbraco",
|
||||
"homepage": "https://github.com/umbraco/umbraco-cms/",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/umbraco/Umbraco-CMS.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://issues.umbraco.org"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.4"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "bower-installer",
|
||||
"test": "karma start test/config/karma.conf.js --singlerun",
|
||||
"build": "gulp"
|
||||
},
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"npm": "^6.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.5.0",
|
||||
"bower-installer": "^1.2.0",
|
||||
|
||||
+177
-78
@@ -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;
|
||||
@@ -152,15 +152,24 @@
|
||||
*/
|
||||
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;
|
||||
$scope.subButtons = null;
|
||||
$scope.page.showSaveButton = false;
|
||||
$scope.page.showPreviewButton = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// create the save button
|
||||
if(_.contains($scope.content.allowedActions, "A")) {
|
||||
$scope.page.showSaveButton = true;
|
||||
// add ellipsis to the save button if it opens the variant overlay
|
||||
$scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false";
|
||||
}
|
||||
|
||||
// create the pubish combo button
|
||||
$scope.page.buttonGroupState = "init";
|
||||
var buttons = contentEditingHelper.configureContentEditorButtons({
|
||||
create: $scope.page.isNew,
|
||||
@@ -168,11 +177,10 @@
|
||||
methods: {
|
||||
saveAndPublish: $scope.saveAndPublish,
|
||||
sendToPublish: $scope.sendToPublish,
|
||||
save: $scope.save,
|
||||
unPublish: $scope.unPublish
|
||||
unpublish: $scope.unpublish
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$scope.defaultButton = buttons.defaultButton;
|
||||
$scope.subButtons = buttons.subButtons;
|
||||
$scope.page.showPreviewButton = true;
|
||||
@@ -202,7 +210,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;
|
||||
@@ -213,7 +221,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),
|
||||
@@ -223,11 +231,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) {
|
||||
|
||||
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
//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({
|
||||
@@ -237,19 +323,17 @@
|
||||
action: args.action,
|
||||
showNotifications: args.showNotifications
|
||||
}).then(function (data) {
|
||||
//success
|
||||
//success
|
||||
init($scope.content);
|
||||
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
|
||||
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
|
||||
@@ -257,7 +341,7 @@
|
||||
editorState.set($scope.content);
|
||||
}
|
||||
|
||||
$scope.page.buttonGroupState = "error";
|
||||
resetNestedFieldValiation(fieldsToRollback);
|
||||
|
||||
return $q.reject(err);
|
||||
});
|
||||
@@ -332,52 +416,45 @@
|
||||
});
|
||||
}
|
||||
|
||||
$scope.unPublish = function () {
|
||||
$scope.unpublish = function() {
|
||||
clearNotifications($scope.content);
|
||||
if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) {
|
||||
var dialog = {
|
||||
parentScope: $scope,
|
||||
view: "views/content/overlays/unpublish.html",
|
||||
variants: $scope.content.variants, //set a model property for the dialog
|
||||
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
|
||||
submitButtonLabelKey: "content_unpublish",
|
||||
submit: function (model) {
|
||||
|
||||
//if there's any variants than we need to set the language and include the variants to publish
|
||||
var culture = null;
|
||||
if ($scope.content.variants.length > 0) {
|
||||
_.each($scope.content.variants,
|
||||
function (d) {
|
||||
//set the culture if this is active
|
||||
if (d.active === true) {
|
||||
culture = d.language.culture;
|
||||
}
|
||||
});
|
||||
model.submitButtonState = "busy";
|
||||
|
||||
var selectedVariants = _.filter(model.variants, function(variant) { return variant.save; });
|
||||
var culturesForUnpublishing = _.map(selectedVariants, function(variant) { return variant.language.culture; });
|
||||
|
||||
contentResource.unpublish($scope.content.id, culturesForUnpublishing)
|
||||
.then(function (data) {
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
contentEditingHelper.reBindChangedProperties($scope.content, data);
|
||||
init($scope.content);
|
||||
syncTreeNode($scope.content, data.path);
|
||||
$scope.page.buttonGroupState = "success";
|
||||
eventsService.emit("content.unpublished", { content: $scope.content });
|
||||
overlayService.close();
|
||||
}, function (err) {
|
||||
$scope.page.buttonGroupState = 'error';
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
close: function () {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
overlayService.open(dialog);
|
||||
}
|
||||
|
||||
if (formHelper.submitForm({ scope: $scope, skipValidation: true })) {
|
||||
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
eventsService.emit("content.unpublishing", { content: $scope.content });
|
||||
|
||||
contentResource.unPublish($scope.content.id, culture)
|
||||
.then(function (data) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
|
||||
contentEditingHelper.handleSuccessfulSave({
|
||||
scope: $scope,
|
||||
savedContent: data,
|
||||
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
|
||||
});
|
||||
|
||||
init($scope.content);
|
||||
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
|
||||
eventsService.emit("content.unpublished", { content: $scope.content });
|
||||
|
||||
}, function (err) {
|
||||
$scope.page.buttonGroupState = 'error';
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
$scope.sendToPublish = function () {
|
||||
clearNotifications($scope.content);
|
||||
if (showSaveOrPublishDialog()) {
|
||||
@@ -400,18 +477,25 @@
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
overlayService.open(dialog);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return performSave({ saveMethod: contentResource.sendToPublish, action: "sendToPublish" });
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
return performSave({
|
||||
saveMethod: contentResource.sendToPublish,
|
||||
action: "sendToPublish"
|
||||
}).then(function(){
|
||||
$scope.page.buttonGroupState = "success";
|
||||
}, function () {
|
||||
$scope.page.buttonGroupState = "error";
|
||||
});;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.saveAndPublish = function () {
|
||||
clearNotifications($scope.content);
|
||||
// TODO: Add "..." to publish button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
|
||||
if (showSaveOrPublishDialog()) {
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
|
||||
@@ -457,7 +541,15 @@
|
||||
else {
|
||||
//ensure the publish flag is set
|
||||
$scope.content.variants[0].publish = true;
|
||||
return performSave({ saveMethod: contentResource.publish, action: "publish" });
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
return performSave({
|
||||
saveMethod: contentResource.publish,
|
||||
action: "publish"
|
||||
}).then(function(){
|
||||
$scope.page.buttonGroupState = "success";
|
||||
}, function () {
|
||||
$scope.page.buttonGroupState = "error";
|
||||
});;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -488,15 +580,14 @@
|
||||
clearNotifications($scope.content);
|
||||
overlayService.close();
|
||||
return $q.when(data);
|
||||
},
|
||||
function (err) {
|
||||
clearDirtyState($scope.content.variants);
|
||||
model.submitButtonState = "error";
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = $scope.content.variants;
|
||||
//don't reject, we've handled the error
|
||||
return $q.when(err);
|
||||
});
|
||||
}, function (err) {
|
||||
clearDirtyState($scope.content.variants);
|
||||
model.submitButtonState = "error";
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = $scope.content.variants;
|
||||
//don't reject, we've handled the error
|
||||
return $q.when(err);
|
||||
});
|
||||
},
|
||||
close: function (oldModel) {
|
||||
overlayService.close();
|
||||
@@ -507,7 +598,15 @@
|
||||
}
|
||||
}
|
||||
else {
|
||||
return performSave({ saveMethod: $scope.saveMethod(), action: "save" });
|
||||
$scope.page.saveButtonState = "busy";
|
||||
return performSave({
|
||||
saveMethod: $scope.saveMethod(),
|
||||
action: "save"
|
||||
}).then(function(){
|
||||
$scope.page.saveButtonState = "success";
|
||||
}, function () {
|
||||
$scope.page.saveButtonState = "error";
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -517,9 +616,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.
|
||||
|
||||
+32
-8
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper, editorService) {
|
||||
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper, editorService, redirectUrlsResource) {
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
|
||||
@@ -72,6 +72,9 @@
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
//default setting for redirect url management
|
||||
scope.urlTrackerDisabled = false;
|
||||
|
||||
// Declare a fallback URL for the <umb-node-preview/> directive
|
||||
if (scope.documentType !== null) {
|
||||
scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id;
|
||||
@@ -139,7 +142,7 @@
|
||||
|
||||
// get current backoffice user and format dates
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
angular.forEach(data.items, function(item) {
|
||||
angular.forEach(data.items, function (item) {
|
||||
item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL');
|
||||
});
|
||||
});
|
||||
@@ -156,6 +159,25 @@
|
||||
});
|
||||
|
||||
}
|
||||
function loadRedirectUrls() {
|
||||
scope.loadingRedirectUrls = true;
|
||||
//check if Redirect Url Management is enabled
|
||||
redirectUrlsResource.getEnableState().then(function (response) {
|
||||
scope.urlTrackerDisabled = response.enabled !== true;
|
||||
if (scope.urlTrackerDisabled === false) {
|
||||
|
||||
redirectUrlsResource.getRedirectsForContentItem(scope.node.udi)
|
||||
.then(function (data) {
|
||||
scope.redirectUrls = data.searchResults;
|
||||
scope.hasRedirects = (typeof data.searchResults !== 'undefined' && data.searchResults.length > 0);
|
||||
scope.loadingRedirectUrls = false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
scope.loadingRedirectUrls = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setAuditTrailLogTypeColor(auditTrail) {
|
||||
angular.forEach(auditTrail, function (item) {
|
||||
@@ -164,7 +186,7 @@
|
||||
case "Publish":
|
||||
item.logTypeColor = "success";
|
||||
break;
|
||||
case "UnPublish":
|
||||
case "Unpublish":
|
||||
case "Delete":
|
||||
item.logTypeColor = "danger";
|
||||
break;
|
||||
@@ -312,12 +334,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
// load audit trail when on the info tab
|
||||
// load audit trail and redirects when on the info tab
|
||||
evts.push(eventsService.on("app.tabChange", function (event, args) {
|
||||
$timeout(function(){
|
||||
$timeout(function () {
|
||||
if (args.alias === "umbInfo") {
|
||||
isInfoTab = true;
|
||||
loadAuditTrail();
|
||||
loadRedirectUrls();
|
||||
} else {
|
||||
isInfoTab = false;
|
||||
}
|
||||
@@ -325,13 +348,14 @@
|
||||
}));
|
||||
|
||||
// watch for content state updates
|
||||
scope.$watch('node.updateDate', function(newValue, oldValue){
|
||||
scope.$watch('node.updateDate', function (newValue, oldValue) {
|
||||
|
||||
if(!newValue) { return; }
|
||||
if(newValue === oldValue) { return; }
|
||||
if (!newValue) { return; }
|
||||
if (newValue === oldValue) { return; }
|
||||
|
||||
if(isInfoTab) {
|
||||
loadAuditTrail();
|
||||
loadRedirectUrls();
|
||||
formatDatesToLocal();
|
||||
setNodePublishStatus(scope.node);
|
||||
}
|
||||
|
||||
+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) {
|
||||
|
||||
+29
-25
@@ -11,32 +11,36 @@ function hexBgColor() {
|
||||
restrict: "A",
|
||||
link: function (scope, element, attr, formCtrl) {
|
||||
|
||||
var origColor = null;
|
||||
if (attr.hexBgOrig) {
|
||||
//set the orig based on the attribute if there is one
|
||||
origColor = attr.hexBgOrig;
|
||||
}
|
||||
|
||||
attr.$observe("hexBgColor", function (newVal) {
|
||||
if (newVal) {
|
||||
if (!origColor) {
|
||||
//get the orig color before changing it
|
||||
origColor = element.css("border-color");
|
||||
}
|
||||
//validate it - test with and without the leading hash.
|
||||
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", "#" + newVal);
|
||||
return;
|
||||
}
|
||||
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", newVal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
element.css("background-color", origColor);
|
||||
});
|
||||
// Only add inline hex background color if defined and not "true".
|
||||
if (attr.hexBgInline === undefined || (attr.hexBgInline !== undefined && attr.hexBgInline === "true")) {
|
||||
|
||||
var origColor = null;
|
||||
if (attr.hexBgOrig) {
|
||||
// Set the orig based on the attribute if there is one.
|
||||
origColor = attr.hexBgOrig;
|
||||
}
|
||||
|
||||
attr.$observe("hexBgColor", function (newVal) {
|
||||
if (newVal) {
|
||||
if (!origColor) {
|
||||
// Get the orig color before changing it.
|
||||
origColor = element.css("border-color");
|
||||
}
|
||||
// Validate it - test with and without the leading hash.
|
||||
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", "#" + newVal);
|
||||
return;
|
||||
}
|
||||
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", newVal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
element.css("background-color", origColor);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
angular.module('umbraco.directives').directive("hexBgColor", hexBgColor);
|
||||
angular.module('umbraco.directives').directive("hexBgColor", hexBgColor);
|
||||
|
||||
+15
-3
@@ -41,7 +41,6 @@
|
||||
};
|
||||
|
||||
function setFocalPoint (event) {
|
||||
|
||||
$scope.$emit("imageFocalPointStart");
|
||||
|
||||
var offsetX = event.offsetX - 10;
|
||||
@@ -50,7 +49,6 @@
|
||||
calculateGravity(offsetX, offsetY);
|
||||
|
||||
lazyEndEvent();
|
||||
|
||||
};
|
||||
|
||||
/** Initializes the component */
|
||||
@@ -148,12 +146,26 @@
|
||||
|
||||
/** Sets the width/height/left/top dimentions based on the image size and the "center" value */
|
||||
function setDimensions() {
|
||||
if (htmlImage && vm.center) {
|
||||
|
||||
if (vm.src.endsWith(".svg")) {
|
||||
// svg files don't automatically get a size by
|
||||
// loading them set a default size for now
|
||||
vm.dimensions.width = 200;
|
||||
vm.dimensions.height = 200;
|
||||
vm.dimensions.left = vm.center.left * vm.dimensions.width - 10;
|
||||
vm.dimensions.top = vm.center.top * vm.dimensions.height - 10;
|
||||
// can't crop an svg file, don't show the focal point
|
||||
if (htmlOverlay) {
|
||||
htmlOverlay.remove();
|
||||
}
|
||||
}
|
||||
else if (htmlImage && vm.center) {
|
||||
vm.dimensions.width = htmlImage.width();
|
||||
vm.dimensions.height = htmlImage.height();
|
||||
vm.dimensions.left = vm.center.left * vm.dimensions.width - 10;
|
||||
vm.dimensions.top = vm.center.top * vm.dimensions.height - 10;
|
||||
}
|
||||
|
||||
return vm.dimensions.width;
|
||||
};
|
||||
|
||||
|
||||
+9
-1
@@ -73,7 +73,15 @@ angular.module("umbraco.directives")
|
||||
if (node.selected) {
|
||||
css.push("umb-tree-node-checked");
|
||||
}
|
||||
|
||||
|
||||
//is this the current action node (this is not the same as the current selected node!)
|
||||
var actionNode = appState.getMenuState("currentNode");
|
||||
if (actionNode) {
|
||||
if (actionNode.id === node.id) {
|
||||
css.push("active");
|
||||
}
|
||||
}
|
||||
|
||||
return css.join(" ");
|
||||
};
|
||||
|
||||
|
||||
+11
-4
@@ -1,5 +1,4 @@
|
||||
|
||||
/**
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbColorSwatches
|
||||
@restrict E
|
||||
@@ -15,9 +14,10 @@ Use this directive to generate color swatches to pick from.
|
||||
</umb-color-swatches>
|
||||
</pre>
|
||||
@param {array} colors (<code>attribute</code>): The array of colors.
|
||||
@param {string} colors (<code>attribute</code>): The array of colors.
|
||||
@param {string} selectedColor (<code>attribute</code>): The selected color.
|
||||
@param {string} size (<code>attribute</code>): The size (s, m).
|
||||
@param {string} useLabel (<code>attribute</code>): Specify if labels should be used.
|
||||
@param {string} useColorClass (<code>attribute</code>): Specify if color values are css classes.
|
||||
@param {function} onSelect (<code>expression</code>): Callback function when the item is selected.
|
||||
**/
|
||||
|
||||
@@ -28,6 +28,11 @@ Use this directive to generate color swatches to pick from.
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
// Set default to true if not defined
|
||||
if (angular.isUndefined(scope.useColorClass)) {
|
||||
scope.useColorClass = false;
|
||||
}
|
||||
|
||||
scope.setColor = function (color) {
|
||||
scope.selectedColor = color;
|
||||
if (scope.onSelect) {
|
||||
@@ -45,7 +50,9 @@ Use this directive to generate color swatches to pick from.
|
||||
colors: '=?',
|
||||
size: '@',
|
||||
selectedColor: '=',
|
||||
onSelect: '&'
|
||||
onSelect: '&',
|
||||
useLabel: '=',
|
||||
useColorClass: '=?'
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
+11
-1
@@ -125,6 +125,14 @@ Use this directive to generate a thumbnail grid of media items.
|
||||
i--;
|
||||
}
|
||||
|
||||
if (scope.includeSubFolders !== 'true') {
|
||||
if (item.parentId !== parseInt(scope.currentFolderId)) {
|
||||
scope.items.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (scope.items.length > 0) {
|
||||
@@ -316,7 +324,9 @@ Use this directive to generate a thumbnail grid of media items.
|
||||
itemMaxHeight: "@",
|
||||
itemMinWidth: "@",
|
||||
itemMinHeight: "@",
|
||||
onlyImages: "@"
|
||||
onlyImages: "@",
|
||||
includeSubFolders: "@",
|
||||
currentFolderId: "@"
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
+2
-1
@@ -221,7 +221,8 @@
|
||||
isClientSide: true
|
||||
});
|
||||
|
||||
newVal += files[i].name + ",";
|
||||
//special check for a comma in the name
|
||||
newVal += files[i].name.replace(',', '-') + ",";
|
||||
|
||||
if (isImage) {
|
||||
|
||||
|
||||
+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;
|
||||
|
||||
@@ -145,7 +145,7 @@ angular.module('umbraco.mocks').
|
||||
"content_statistics": "Statistics",
|
||||
"content_titleOptional": "Title (optional)",
|
||||
"content_type": "Type",
|
||||
"content_unPublish": "Unpublish",
|
||||
"content_unpublish": "Unpublish",
|
||||
"content_updateDate": "Last edited",
|
||||
"content_updateDateDesc": "Date/time this document was created",
|
||||
"content_uploadClear": "Remove file",
|
||||
|
||||
@@ -206,7 +206,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.contentResource#unPublish
|
||||
* @name umbraco.resources.contentResource#unpublish
|
||||
* @methodOf umbraco.resources.contentResource
|
||||
*
|
||||
* @description
|
||||
@@ -214,7 +214,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
* contentResource.unPublish(1234)
|
||||
* contentResource.unpublish(1234)
|
||||
* .then(function() {
|
||||
* alert("node was unpulished");
|
||||
* }, function(err){
|
||||
@@ -225,21 +225,20 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object.
|
||||
*
|
||||
*/
|
||||
unPublish: function (id, culture) {
|
||||
unpublish: function (id, cultures) {
|
||||
if (!id) {
|
||||
throw "id cannot be null";
|
||||
}
|
||||
|
||||
if (!culture) {
|
||||
culture = null;
|
||||
if (!cultures) {
|
||||
cultures = [];
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"PostUnPublish",
|
||||
{ id: id, culture: culture })),
|
||||
"PostUnpublish"), { id: id, cultures: cultures }),
|
||||
'Failed to publish content with id ' + id);
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -7,10 +7,60 @@
|
||||
**/
|
||||
function logResource($q, $http, umbRequestHelper) {
|
||||
|
||||
function isValidDate(input) {
|
||||
if (input) {
|
||||
if (Object.prototype.toString.call(input) === "[object Date]" && !isNaN(input.getTime())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
function dateToValidIsoString(input) {
|
||||
if (isValidDate(input)) {
|
||||
return input.toISOString();
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
//the factory object returned
|
||||
return {
|
||||
|
||||
getPagedEntityLog: function (options) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.logResource#getPagedEntityLog
|
||||
* @methodOf umbraco.resources.logResource
|
||||
*
|
||||
* @description
|
||||
* Gets a paginated log history for a entity
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
* var options = {
|
||||
* id : 1234
|
||||
* pageSize : 10,
|
||||
* pageNumber : 1,
|
||||
* orderDirection : "Descending",
|
||||
* sinceDate : new Date(2018,0,1)
|
||||
* };
|
||||
* logResource.getPagedEntityLog(options)
|
||||
* .then(function(log) {
|
||||
* alert('its here!');
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param {Object} options options object
|
||||
* @param {Int} options.id the id of the entity
|
||||
* @param {Int} options.pageSize if paging data, number of nodes per page, default = 10, set to 0 to disable paging
|
||||
* @param {Int} options.pageNumber if paging data, current page index, default = 1
|
||||
* @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Descending`
|
||||
* @param {Date} options.sinceDate if provided this will only get log entries going back to this date
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getPagedEntityLog: function(options) {
|
||||
|
||||
var defaults = {
|
||||
pageSize: 10,
|
||||
@@ -24,11 +74,15 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
angular.extend(defaults, options);
|
||||
//now copy back to the options we will use
|
||||
options = defaults;
|
||||
|
||||
if (options.hasOwnProperty('sinceDate')) {
|
||||
options.sinceDate = dateToValidIsoString(options.sinceDate);
|
||||
}
|
||||
|
||||
//change asc/desct
|
||||
if (options.orderDirection === "asc") {
|
||||
options.orderDirection = "Ascending";
|
||||
}
|
||||
else if (options.orderDirection === "desc") {
|
||||
} else if (options.orderDirection === "desc") {
|
||||
options.orderDirection = "Descending";
|
||||
}
|
||||
|
||||
@@ -45,7 +99,37 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
'Failed to retrieve log data for id');
|
||||
},
|
||||
|
||||
getPagedUserLog: function (options) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.logResource#getPagedUserLog
|
||||
* @methodOf umbraco.resources.logResource
|
||||
*
|
||||
* @description
|
||||
* Gets a paginated log history for the current user
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
* var options = {
|
||||
* pageSize : 10,
|
||||
* pageNumber : 1,
|
||||
* orderDirection : "Descending",
|
||||
* sinceDate : new Date(2018,0,1)
|
||||
* };
|
||||
* logResource.getPagedUserLog(options)
|
||||
* .then(function(log) {
|
||||
* alert('its here!');
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param {Object} options options object
|
||||
* @param {Int} options.pageSize if paging data, number of nodes per page, default = 10, set to 0 to disable paging
|
||||
* @param {Int} options.pageNumber if paging data, current page index, default = 1
|
||||
* @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Descending`
|
||||
* @param {Date} options.sinceDate if provided this will only get log entries going back to this date
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getPagedUserLog: function(options) {
|
||||
|
||||
var defaults = {
|
||||
pageSize: 10,
|
||||
@@ -59,11 +143,15 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
angular.extend(defaults, options);
|
||||
//now copy back to the options we will use
|
||||
options = defaults;
|
||||
|
||||
if (options.hasOwnProperty('sinceDate')) {
|
||||
options.sinceDate = dateToValidIsoString(options.sinceDate);
|
||||
}
|
||||
|
||||
//change asc/desct
|
||||
if (options.orderDirection === "asc") {
|
||||
options.orderDirection = "Ascending";
|
||||
}
|
||||
else if (options.orderDirection === "desc") {
|
||||
} else if (options.orderDirection === "desc") {
|
||||
options.orderDirection = "Descending";
|
||||
}
|
||||
|
||||
@@ -71,7 +159,7 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetPagedEntityLog",
|
||||
"GetPagedCurrentUserLog",
|
||||
options)),
|
||||
'Failed to retrieve log data for id');
|
||||
},
|
||||
@@ -82,6 +170,7 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
* @methodOf umbraco.resources.logResource
|
||||
*
|
||||
* @description
|
||||
* <strong>[OBSOLETE] use getPagedEntityLog instead</strong><br />
|
||||
* Gets the log history for a give entity id
|
||||
*
|
||||
* ##usage
|
||||
@@ -96,23 +185,24 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getEntityLog: function (id) {
|
||||
getEntityLog: function(id) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetEntityLog",
|
||||
[{ id: id }])),
|
||||
'Failed to retrieve user data for id ' + id);
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetEntityLog",
|
||||
[{ id: id }])),
|
||||
'Failed to retrieve user data for id ' + id);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.logResource#getUserLog
|
||||
* @methodOf umbraco.resources.logResource
|
||||
*
|
||||
* @description
|
||||
* Gets the current users' log history for a given type of log entry
|
||||
* <strong>[OBSOLETE] use getPagedUserLog instead</strong><br />
|
||||
* Gets the current user's log history for a given type of log entry
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
@@ -127,14 +217,14 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getUserLog: function (type, since) {
|
||||
getUserLog: function(type, since) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetCurrentUserLog",
|
||||
[{ logtype: type}, {sinceDate: since }])),
|
||||
'Failed to retrieve log data for current user of type ' + type + ' since ' + since);
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetCurrentUserLog",
|
||||
[{ logtype: type }, { sinceDate: dateToValidIsoString(since) }])),
|
||||
'Failed to retrieve log data for current user of type ' + type + ' since ' + since);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -158,16 +248,16 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getLog: function (type, since) {
|
||||
getLog: function(type, since) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetLog",
|
||||
[{ logtype: type}, {sinceDate: since }])),
|
||||
'Failed to retrieve log data of type ' + type + ' since ' + since);
|
||||
}
|
||||
};
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetLog",
|
||||
[{ logtype: type }, { sinceDate: dateToValidIsoString(since) }])),
|
||||
'Failed to retrieve log data of type ' + type + ' since ' + since);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('umbraco.resources').factory('logResource', logResource);
|
||||
|
||||
@@ -40,6 +40,32 @@
|
||||
{ searchTerm: searchTerm, page: pageIndex, pageSize: pageSize })),
|
||||
'Failed to retrieve data for searching redirect urls');
|
||||
}
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name umbraco.resources.redirectUrlResource#getRedirectsForContentItem
|
||||
* @methodOf umbraco.resources.redirectUrlResource
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Used to retrieve RedirectUrls for a specific item of content for Information tab
|
||||
* ##usage
|
||||
* <pre>
|
||||
* redirectUrlsResource.getRedirectsForContentItem("udi:123456")
|
||||
* .then(function(response) {
|
||||
*
|
||||
* });
|
||||
* </pre>
|
||||
* @param {String} contentUdi identifier for the content item to retrieve redirects for
|
||||
*/
|
||||
function getRedirectsForContentItem(contentUdi) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"redirectUrlManagementApiBaseUrl",
|
||||
"RedirectUrlsForContentItem",
|
||||
{ contentUdi: contentUdi })),
|
||||
'Failed to retrieve redirects for content: ' + contentUdi);
|
||||
}
|
||||
|
||||
function getEnableState() {
|
||||
|
||||
@@ -50,7 +76,7 @@
|
||||
"GetEnableState")),
|
||||
'Failed to retrieve data to check if the 301 redirect is enabled');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name umbraco.resources.redirectUrlResource#deleteRedirectUrl
|
||||
@@ -107,7 +133,8 @@
|
||||
searchRedirectUrls: searchRedirectUrls,
|
||||
deleteRedirectUrl: deleteRedirectUrl,
|
||||
toggleUrlTracker: toggleUrlTracker,
|
||||
getEnableState: getEnableState
|
||||
getEnableState: getEnableState,
|
||||
getRedirectsForContentItem: getRedirectsForContentItem
|
||||
};
|
||||
|
||||
return resource;
|
||||
|
||||
@@ -65,17 +65,14 @@ angular.module('umbraco.services')
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads in moment.js requirements during the _loadInitAssets call
|
||||
*/
|
||||
function loadMomentLocaleForCurrentUser() {
|
||||
function getMomentLocales(locales, supportedLocales) {
|
||||
|
||||
var self = this;
|
||||
|
||||
function loadLocales(currentUser, supportedLocales) {
|
||||
var locale = currentUser.locale.toLowerCase();
|
||||
var localeUrls = [];
|
||||
var locales = locales.split(',');
|
||||
for (var i = 0; i < locales.length; i++) {
|
||||
var locale = locales[i].toString().toLowerCase();
|
||||
if (locale !== 'en-us') {
|
||||
var localeUrls = [];
|
||||
|
||||
if (supportedLocales.indexOf(locale + '.js') > -1) {
|
||||
localeUrls.push('lib/moment/' + locale + '.js');
|
||||
}
|
||||
@@ -85,16 +82,35 @@ angular.module('umbraco.services')
|
||||
localeUrls.push('lib/moment/' + majorLocale);
|
||||
}
|
||||
}
|
||||
return self.load(localeUrls, $rootScope);
|
||||
}
|
||||
else {
|
||||
$q.when(true);
|
||||
}
|
||||
}
|
||||
|
||||
return localeUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads specific Moment.js Locales.
|
||||
* @param {any} locales
|
||||
* @param {any} supportedLocales
|
||||
*/
|
||||
function loadLocales(locales, supportedLocales) {
|
||||
var localeUrls = getMomentLocales(locales, supportedLocales);
|
||||
if (localeUrls.length >= 1) {
|
||||
return assetsService.load(localeUrls, $rootScope);
|
||||
}
|
||||
else {
|
||||
$q.when(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads in moment.js requirements during the _loadInitAssets call
|
||||
*/
|
||||
function loadMomentLocaleForCurrentUser() {
|
||||
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
return javascriptLibraryResource.getSupportedLocalesForMoment().then(function (supportedLocales) {
|
||||
return loadLocales(currentUser, supportedLocales);
|
||||
return loadLocales(currentUser.locale, supportedLocales);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,7 +152,9 @@ angular.module('umbraco.services')
|
||||
return $q.when(true);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
loadLocales: loadLocales,
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.assetsService#loadCss
|
||||
|
||||
@@ -146,7 +146,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
if (!args.methods) {
|
||||
throw "args.methods is not defined";
|
||||
}
|
||||
if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.save || !args.methods.unPublish) {
|
||||
if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish) {
|
||||
throw "args.methods does not contain all required defined methods";
|
||||
}
|
||||
|
||||
@@ -179,26 +179,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
alias: "sendToPublish",
|
||||
addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false"
|
||||
};
|
||||
case "A":
|
||||
//save
|
||||
return {
|
||||
letter: ch,
|
||||
labelKey: "buttons_save",
|
||||
handler: args.methods.save,
|
||||
hotKey: "ctrl+s",
|
||||
hotKeyWhenHidden: true,
|
||||
alias: "save",
|
||||
addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false"
|
||||
};
|
||||
case "Z":
|
||||
//unpublish
|
||||
return {
|
||||
letter: ch,
|
||||
labelKey: "content_unPublish",
|
||||
handler: args.methods.unPublish,
|
||||
labelKey: "content_unpublish",
|
||||
handler: args.methods.unpublish,
|
||||
hotKey: "ctrl+u",
|
||||
hotKeyWhenHidden: true,
|
||||
alias: "unpublish"
|
||||
alias: "unpublish",
|
||||
addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false"
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
@@ -209,8 +199,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
buttons.subButtons = [];
|
||||
|
||||
//This is the ideal button order but depends on circumstance, we'll use this array to create the button list
|
||||
// Publish, SendToPublish, Save
|
||||
var buttonOrder = ["U", "H", "A"];
|
||||
// Publish, SendToPublish
|
||||
var buttonOrder = ["U", "H"];
|
||||
|
||||
//Create the first button (primary button)
|
||||
//We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item.
|
||||
@@ -252,7 +242,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
// so long as it's already published and if the user has access to publish
|
||||
// and the user has access to unpublish (may have been removed via Event)
|
||||
if (!args.create) {
|
||||
if (args.content.publishDate && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) {
|
||||
var hasPublishedVariant = args.content.variants.filter(function(variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0;
|
||||
if (hasPublishedVariant && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) {
|
||||
buttons.subButtons.push(createButtonDefinition("Z"));
|
||||
}
|
||||
}
|
||||
@@ -412,8 +403,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
case "Z":
|
||||
return {
|
||||
letter: ch,
|
||||
labelKey: "content_unPublish",
|
||||
handler: "unPublish"
|
||||
labelKey: "content_unpublish",
|
||||
handler: "unpublish"
|
||||
};
|
||||
|
||||
default:
|
||||
|
||||
@@ -85,6 +85,7 @@ function navigationService($rootScope, $route, $routeParams, $log, $location, $q
|
||||
appState.setSectionState("showSearchResults", false);
|
||||
appState.setGlobalState("stickyNavigation", false);
|
||||
appState.setGlobalState("showTray", false);
|
||||
appState.setMenuState("currentNode", null);
|
||||
|
||||
if (appState.getGlobalState("isTablet") === true) {
|
||||
appState.setGlobalState("showNavigation", false);
|
||||
@@ -365,7 +366,8 @@ function navigationService($rootScope, $route, $routeParams, $log, $location, $q
|
||||
|
||||
if (appState.getGlobalState("isTablet") === true && !appState.getGlobalState("stickyNavigation")) {
|
||||
//reset it to whatever is in the url
|
||||
appState.setSectionState("currentSection", $routeParams.section);
|
||||
appState.setSectionState("currentSection", $routeParams.section);
|
||||
|
||||
setMode("default-hidesectiontree");
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,6 @@ angular.module('umbraco.services')
|
||||
/** Called to update the current user's timeout */
|
||||
function setUserTimeoutInternal(newTimeout) {
|
||||
|
||||
|
||||
var asNumber = parseFloat(newTimeout);
|
||||
if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
|
||||
currentUser.remainingAuthSeconds = newTimeout;
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
@import "components/umb-querybuilder.less";
|
||||
@import "components/umb-pagination.less";
|
||||
@import "components/umb-mini-list-view.less";
|
||||
@import "components/umb-multiple-textbox.less";
|
||||
@import "components/umb-badge.less";
|
||||
@import "components/umb-nested-content.less";
|
||||
@import "components/umb-checkmark.less";
|
||||
|
||||
@@ -227,13 +227,29 @@ input[type="button"] {
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
background: @green;
|
||||
color: white;
|
||||
color: @white;
|
||||
font-weight: bold;
|
||||
&:hover {
|
||||
background: @green-d1;
|
||||
}
|
||||
}
|
||||
|
||||
// outlined
|
||||
.btn-outline {
|
||||
border: 1px solid @gray-8;
|
||||
background: @white;
|
||||
color: @black;
|
||||
padding: 5px 13px;
|
||||
}
|
||||
|
||||
.btn-outline:hover,
|
||||
.btn-outline:focus,
|
||||
.btn-outline:active {
|
||||
border-color: @gray-7;
|
||||
background: transparent;
|
||||
color: @black;
|
||||
}
|
||||
|
||||
// Cross-browser Jank
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
@@ -790,13 +790,13 @@ h4.panel-title {
|
||||
}
|
||||
|
||||
.smartphone-portrait {
|
||||
width: 320px;
|
||||
height: 504px;
|
||||
width: 360px;
|
||||
height: 640px;
|
||||
}
|
||||
|
||||
.smartphone-landscape {
|
||||
width: 480px;
|
||||
height: 256px;
|
||||
width: 640px;
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
.border {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
.umb-dashboard__content {
|
||||
padding: 30px 20px;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
.umb-button-group {
|
||||
|
||||
.umb-button__button {
|
||||
border-radius: 3px 0px 0px 3px;
|
||||
border-radius: @baseBorderRadius;
|
||||
}
|
||||
|
||||
.umb-button-group__toggle {
|
||||
border-radius: 0px 3px 3px 0;
|
||||
border-radius: 0px @baseBorderRadius @baseBorderRadius 0;
|
||||
border-left: 1px solid rgba(0,0,0,0.09);
|
||||
margin-left: -2px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
height: 105px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
@@ -114,6 +113,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
background: @gray-10;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: -20px;
|
||||
margin-top: -10px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
.umb-prevalues-multivalues__left {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.umb-prevalues-multivalues__right {
|
||||
|
||||
@@ -94,6 +94,10 @@ body.touch .umb-tree {
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
|
||||
&.active {
|
||||
background: @gray-10;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @gray-10;
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
.umb-color-swatches {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
.umb-color-box {
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
padding: 1px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
border-radius: 3px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
transition: box-shadow .3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover, &:focus {
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||
@@ -28,4 +32,55 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.with-labels {
|
||||
|
||||
.umb-color-box {
|
||||
width: 120px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
.umb-color-box-inner {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
min-height: 80px;
|
||||
padding-top: 10px;
|
||||
|
||||
.umb-color-box__label {
|
||||
background: #fff;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
flex: 0 0 100%;
|
||||
padding: 1px 5px;
|
||||
max-width: 100%;
|
||||
margin-top: auto;
|
||||
margin-bottom: -3px;
|
||||
margin-left: -1px;
|
||||
margin-right: -1px;
|
||||
text-indent: 0;
|
||||
text-align: left;
|
||||
border: 1px solid @gray-8;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
overflow: hidden;
|
||||
|
||||
.umb-color-box__name {
|
||||
color: @black;
|
||||
font-weight: bold;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.umb-color-box__description {
|
||||
font-size: 12px;
|
||||
line-height: 1.5em;
|
||||
color: @gray-3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,18 @@
|
||||
background-color: @white;
|
||||
}
|
||||
|
||||
.umb-media-grid__item-file-icon > span {
|
||||
color: @white;
|
||||
background: @gray-4;
|
||||
padding: 1px 3px;
|
||||
font-size: 10px;
|
||||
line-height: 130%;
|
||||
display: block;
|
||||
margin-top: -30px;
|
||||
margin-left: -10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.umb-media-grid__item.-selected {
|
||||
box-shadow: 0 2px 8px 0 rgba(0,0,0,.35);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
.umb-multiple-textbox .textbox-wrapper {
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .textbox-wrapper .umb-editor {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .textbox-wrapper i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .textbox-wrapper i.handle {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .textbox-wrapper a.remove {
|
||||
margin-left: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .add-link {
|
||||
&:extend(.umb-node-preview-add);
|
||||
}
|
||||
|
||||
.umb-editor-wrapper .umb-multiple-textbox .add-link {
|
||||
&:extend(.umb-editor-wrapper .umb-node-preview);
|
||||
}
|
||||
|
||||
.umb-modal .umb-multiple-textbox .textbox-wrapper .umb-editor {
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
}
|
||||
@@ -166,7 +166,7 @@ input.umb-table__input {
|
||||
|
||||
}
|
||||
|
||||
.-content .-unpublished {
|
||||
.-content :not(.with-unpublished-version).-unpublished {
|
||||
.umb-table__name > * {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
@@ -27,5 +27,5 @@
|
||||
|
||||
.umb-permission__description {
|
||||
font-size: 13px;
|
||||
color: @gray-5;
|
||||
color: @gray-4;
|
||||
}
|
||||
|
||||
@@ -133,27 +133,6 @@ div.umb-codeeditor .umb-btn-toolbar {
|
||||
//
|
||||
// Color picker
|
||||
// --------------------------------------------------
|
||||
ul.color-picker li {
|
||||
padding: 2px;
|
||||
margin: 3px;
|
||||
border: 2px solid transparent;
|
||||
width: 60px;
|
||||
|
||||
.thumbnail{
|
||||
min-width: auto;
|
||||
width: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
height: 50px;
|
||||
display:flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor:pointer;
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* pre-value editor */
|
||||
.control-group.color-picker-preval {
|
||||
@@ -174,7 +153,7 @@ ul.color-picker li {
|
||||
div.color-picker-prediv {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 80%;
|
||||
max-width: 85%;
|
||||
|
||||
pre {
|
||||
display: inline-flex;
|
||||
@@ -357,13 +336,30 @@ ul.color-picker li {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-icon-holder .icon{
|
||||
.umb-sortable-thumbnails .umb-icon-holder .icon {
|
||||
font-size: 40px;
|
||||
line-height: 50px;
|
||||
color: @gray-3;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-icon-holder .file-icon > span {
|
||||
color: @white;
|
||||
background: @gray-4;
|
||||
padding: 1px 3px;
|
||||
font-size: 10px;
|
||||
line-height: 130%;
|
||||
display: block;
|
||||
margin-top: -30px;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-icon-holder .file-icon + small {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
.umb-sortable-thumbnails .umb-sortable-thumbnails__wrapper {
|
||||
width: 124px;
|
||||
height: 124px;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
function init() {
|
||||
// Check if it is a new user
|
||||
var inviteVal = $location.search().invite;
|
||||
//1 = enter password, 2 = password set, 3 = invalid token
|
||||
if (inviteVal && (inviteVal === "1" || inviteVal === "2")) {
|
||||
|
||||
$q.all([
|
||||
@@ -58,6 +59,8 @@
|
||||
$scope.inviteStep = Number(inviteVal);
|
||||
|
||||
});
|
||||
} else if (inviteVal && inviteVal === "3") {
|
||||
$scope.inviteStep = Number(inviteVal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,10 +104,18 @@
|
||||
</umb-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div ng-show="invitedUser == null && inviteStep === 3" ng-if="inviteStep === 3" class="umb-login-container">
|
||||
<div class="form">
|
||||
<h1 style="margin-bottom: 10px; text-align: left;">Hi there</h1>
|
||||
<p style="line-height: 1.6; margin-bottom: 25px;">
|
||||
<localize key="user_userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</localize>
|
||||
</p>
|
||||
|
||||
<div ng-show="invitedUser == null" class="umb-login-container">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="invitedUser == null && !inviteStep" class="umb-login-container">
|
||||
|
||||
<div class="form">
|
||||
<h1>{{greeting}}</h1>
|
||||
|
||||
+1
-1
@@ -313,7 +313,7 @@ angular.module("umbraco")
|
||||
|
||||
function searchMedia() {
|
||||
$scope.loading = true;
|
||||
entityResource.getPagedDescendants($scope.startNodeId, "Media", $scope.searchOptions)
|
||||
entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions)
|
||||
.then(function(data) {
|
||||
// update image data to work with image grid
|
||||
angular.forEach(data.items, function(mediaItem) {
|
||||
|
||||
+26
-18
@@ -23,13 +23,19 @@
|
||||
|
||||
<div class="form-search">
|
||||
<i class="icon-search"></i>
|
||||
<input class="umb-search-field search-query -full-width-input"
|
||||
ng-model="searchOptions.filter"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_search"
|
||||
ng-change="changeSearch()"
|
||||
type="text"
|
||||
no-dirty-check />
|
||||
<input class="umb-search-field search-query -full-width-input"
|
||||
ng-model="searchOptions.filter"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_search"
|
||||
ng-change="changeSearch()"
|
||||
type="text"
|
||||
no-dirty-check />
|
||||
<br />
|
||||
<label>
|
||||
<localize key="general_includeFromsubFolders">Include subfolders in search</localize>
|
||||
<input type="checkbox" ng-model="showChilds"
|
||||
ng-change="changeSearch()">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="upload-button">
|
||||
@@ -81,17 +87,19 @@
|
||||
</umb-file-dropzone>
|
||||
|
||||
<umb-media-grid
|
||||
ng-if="!loading"
|
||||
items="images"
|
||||
on-click="clickHandler"
|
||||
on-click-name="clickItemName"
|
||||
on-click-edit="editMediaItem(item)"
|
||||
allow-on-click-edit="{{allowMediaEdit}}"
|
||||
item-max-width="150"
|
||||
item-max-height="150"
|
||||
item-min-width="100"
|
||||
item-min-height="100"
|
||||
only-images={{onlyImages}}>
|
||||
ng-if="!loading"
|
||||
items="images"
|
||||
on-click="clickHandler"
|
||||
on-click-name="clickItemName"
|
||||
on-click-edit="editMediaItem(item)"
|
||||
allow-on-click-edit="{{allowMediaEdit}}"
|
||||
item-max-width="150"
|
||||
item-max-height="150"
|
||||
item-min-width="100"
|
||||
item-min-height="100"
|
||||
only-images={{onlyImages}}
|
||||
include-sub-folders={{showChilds}}
|
||||
current-Folder-id="{{currentFolder.id}}">
|
||||
</umb-media-grid>
|
||||
|
||||
<div class="flex justify-center">
|
||||
|
||||
@@ -39,12 +39,23 @@
|
||||
<umb-button
|
||||
alias="preview"
|
||||
ng-if="!page.isNew && content.allowPreview && page.showPreviewButton"
|
||||
type="button"
|
||||
button-style="info"
|
||||
type="button"
|
||||
button-style="outline"
|
||||
action="preview(content)"
|
||||
label="Preview page"
|
||||
label-key="buttons_showPage">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
ng-if="page.showSaveButton"
|
||||
alias="save"
|
||||
type="button"
|
||||
button-style="info"
|
||||
state="page.saveButtonState"
|
||||
action="save(content)"
|
||||
label-key="buttons_save"
|
||||
shortcut="ctrl+s"
|
||||
add-ellipsis="{{page.saveButtonEllipsis}}">
|
||||
</umb-button>
|
||||
|
||||
<umb-button-group
|
||||
ng-if="defaultButton && !content.trashed && !infiniteModel.infiniteMode"
|
||||
|
||||
@@ -22,6 +22,23 @@
|
||||
</ul>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
<umb-box data-element="node-info-redirects" style="display:none;" ng-cloak ng-show="!urlTrackerDisabled && hasRedirects">
|
||||
<umb-box-header title-key="redirectUrls_redirectUrlManagement"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
<div style="position: relative;">
|
||||
<div ng-if="loadingRedirectUrls" style="background: rgba(255, 255, 255, 0.8); position: absolute; top: 0; left: 0; right: 0; bottom: 0;"></div>
|
||||
<umb-load-indicator ng-if="loadingRedirectUrls"></umb-load-indicator>
|
||||
<div ng-show="hasRedirects">
|
||||
<p><localize key="redirectUrls_panelInformation" class="ng-isolate-scope ng-scope">The following URLs redirect to this content item:</localize></p>
|
||||
<ul class="nav nav-stacked" style="margin-bottom: 0;">
|
||||
<li ng-repeat="redirectUrl in redirectUrls">
|
||||
<a href="{{redirectUrl.originalUrl}}" target="_blank"><i ng-class="value.icon" class="icon-out"></i> {{redirectUrl.originalUrl}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box data-element="node-info-history">
|
||||
<umb-box-header title-key="general_history"></umb-box-header>
|
||||
@@ -67,6 +84,7 @@
|
||||
class="history-item__badge"
|
||||
size="xs"
|
||||
color="{{item.logTypeColor}}">
|
||||
|
||||
<localize key="auditTrails_small{{ item.logType }}">{{ item.logType }}</localize>
|
||||
</umb-badge>
|
||||
<span>
|
||||
@@ -179,7 +197,7 @@
|
||||
</div>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group data-element="node-info-create-date" label="@template_createdDate">
|
||||
<umb-control-group ng-if="node.id !== 0" data-element="node-info-create-date" label="@template_createdDate">
|
||||
{{node.createDateFormatted}} <localize key="general_by">by</localize> {{ node.owner.name }}
|
||||
</umb-control-group>
|
||||
|
||||
@@ -209,7 +227,7 @@
|
||||
</div>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group data-element="node-info-id" label="Id">
|
||||
<umb-control-group ng-if="node.id !== 0" data-element="node-info-id" label="Id">
|
||||
<div>{{ node.id }}</div>
|
||||
<small>{{ node.key }}</small>
|
||||
</umb-control-group>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
<div ng-if="notification.view">
|
||||
<div ng-include="notification.view"></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="notification.headline">
|
||||
<a ng-href="{{notification.url}}" target="_blank">
|
||||
<div ng-if="notification.headline" ng-switch on="{{notification}}">
|
||||
<a ng-href="{{notification.url}}" ng-switch-when="{{notification.url && notification.url.trim() != ''}}" target="_blank">
|
||||
<strong>{{notification.headline}}</strong>
|
||||
<span ng-bind-html="notification.message"></span>
|
||||
</a>
|
||||
<div ng-switch-default>
|
||||
<strong>{{notification.headline}}</strong>
|
||||
<span ng-bind-html="notification.message"></span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
<div class="umb-color-swatches">
|
||||
<div class="umb-color-swatches" ng-class="{ 'with-labels': useLabel }">
|
||||
|
||||
<button class="umb-color-box umb-color-box--{{size}} btn-{{color.value}}" ng-repeat="color in colors" title="{{color.name}}" ng-click="setColor(color.value)">
|
||||
<div class="check_circle" ng-if="color.value === selectedColor">
|
||||
<i class="icon icon-check small"></i>
|
||||
<button type="button" class="umb-color-box umb-color-box--{{size}} btn-{{color.value}}" ng-repeat="color in colors" title="{{color.label}}" hex-bg-inline="{{useColorClass === false}}" hex-bg-color="{{color.value}}" ng-class="{ 'active': color.value === selectedColor }" ng-click="setColor(color.value)">
|
||||
<div class="umb-color-box-inner">
|
||||
<div class="check_circle" ng-if="color.value === selectedColor">
|
||||
<i class="icon icon-check small"></i>
|
||||
</div>
|
||||
<div class="umb-color-box__label" ng-if="useLabel">
|
||||
<div class="umb-color-box__name truncate">{{ color.label }}</div>
|
||||
<div class="umb-color-box__description">#{{ color.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user