diff --git a/.gitattributes b/.gitattributes index a664be3a85..c8987ade67 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,7 +13,7 @@ *.png binary *.gif binary -*.cs text=auto diff=csharp +*.cs text=auto diff=csharp *.vb text=auto *.c text=auto *.cpp text=auto @@ -41,9 +41,13 @@ *.fs text=auto *.fsx text=auto *.hs text=auto +*.json text=auto +*.xml text=auto -*.csproj text=auto merge=union -*.vbproj text=auto merge=union -*.fsproj text=auto merge=union -*.dbproj text=auto merge=union -*.sln text=auto eol=crlf merge=union +*.csproj text=auto merge=union +*.vbproj text=auto merge=union +*.fsproj text=auto merge=union +*.dbproj text=auto merge=union +*.sln text=auto eol=crlf merge=union + +*.gitattributes text=auto diff --git a/.gitignore b/.gitignore index a0ff4d5b27..12ad3299ad 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ App_Data/TEMP/* src/Umbraco.Web.UI/[Cc]ss/* src/Umbraco.Web.UI/App_Code/* src/Umbraco.Web.UI/App_Data/* +src/Umbraco.Web.UI/data/* src/Umbraco.Tests/App_Data/* src/Umbraco.Web.UI/[Mm]edia/* src/Umbraco.Web.UI/[Mm]aster[Pp]ages/* diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 658d2f0672..347bde139e 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -28,7 +28,7 @@ - + diff --git a/src/Umbraco.Abstractions/Composing/ComponentCollection.cs b/src/Umbraco.Abstractions/Composing/ComponentCollection.cs index fa4a1849b6..62b240f10f 100644 --- a/src/Umbraco.Abstractions/Composing/ComponentCollection.cs +++ b/src/Umbraco.Abstractions/Composing/ComponentCollection.cs @@ -51,7 +51,7 @@ namespace Umbraco.Core.Composing } catch (Exception ex) { - _logger.Error(componentType, ex, "Error while terminating component {ComponentType}.", componentType.FullName); + _logger.Error(ex, "Error while terminating component {ComponentType}.", componentType.FullName); } } } diff --git a/src/Umbraco.Abstractions/Composing/Composers.cs b/src/Umbraco.Abstractions/Composing/Composers.cs index b2e6c9d068..004c2527e6 100644 --- a/src/Umbraco.Abstractions/Composing/Composers.cs +++ b/src/Umbraco.Abstractions/Composing/Composers.cs @@ -22,28 +22,6 @@ namespace Umbraco.Core.Composing private const int LogThresholdMilliseconds = 100; - /// - /// Initializes a new instance of the class. - /// - /// The composition. - /// The types. - /// The profiling logger. - [Obsolete("This overload only gets the EnableComposer/DisableComposer attributes from the composerTypes assemblies.")] - public Composers(Composition composition, IEnumerable composerTypes, IProfilingLogger logger) - : this(composition, composerTypes, Enumerable.Empty(), logger) - { - var enableDisableAttributes = new List(); - - var assemblies = composerTypes.Select(t => t.Assembly).Distinct(); - foreach (var assembly in assemblies) - { - enableDisableAttributes.AddRange(assembly.GetCustomAttributes(typeof(EnableComposerAttribute))); - enableDisableAttributes.AddRange(assembly.GetCustomAttributes(typeof(DisableComposerAttribute))); - } - - _enableDisableAttributes = enableDisableAttributes; - } - /// /// Initializes a new instance of the class. /// diff --git a/src/Umbraco.Abstractions/Constants-AppSettings.cs b/src/Umbraco.Abstractions/Constants-AppSettings.cs index beaffef5b4..2217863122 100644 --- a/src/Umbraco.Abstractions/Constants-AppSettings.cs +++ b/src/Umbraco.Abstractions/Constants-AppSettings.cs @@ -9,10 +9,6 @@ namespace Umbraco.Core /// public static class AppSettings { - // TODO: Kill me - still used in Umbraco.Core.IO.SystemFiles:27 - [Obsolete("We need to kill this appsetting as we do not use XML content cache umbraco.config anymore due to NuCache")] - public const string ContentXML = "Umbraco.Core.ContentXML"; //umbracoContentXML - /// /// TODO: FILL ME IN /// diff --git a/src/Umbraco.Abstractions/ContentVariationExtensions.cs b/src/Umbraco.Abstractions/ContentVariationExtensions.cs index f3e8943172..a1b374f3c8 100644 --- a/src/Umbraco.Abstractions/ContentVariationExtensions.cs +++ b/src/Umbraco.Abstractions/ContentVariationExtensions.cs @@ -19,6 +19,11 @@ namespace Umbraco.Core /// public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); + /// + /// Determines whether the content type varies by segment. + /// + public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); + /// /// Determines whether the content type is invariant. /// diff --git a/src/Umbraco.Abstractions/Exceptions/ArgumentNullOrEmptyException.cs b/src/Umbraco.Abstractions/Exceptions/ArgumentNullOrEmptyException.cs deleted file mode 100644 index 037d35d0ee..0000000000 --- a/src/Umbraco.Abstractions/Exceptions/ArgumentNullOrEmptyException.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Exceptions -{ - /// - /// The exception that is thrown when a null reference, or an empty argument, is passed to a method that does not accept it as a valid argument. - /// - /// - [Obsolete("Throw an ArgumentNullException when the parameter is null or an ArgumentException when its empty instead.")] - [Serializable] - public class ArgumentNullOrEmptyException : ArgumentNullException - { - /// - /// Initializes a new instance of the class. - /// - public ArgumentNullOrEmptyException() - { } - - /// - /// Initializes a new instance of the class with the name of the parameter that caused this exception. - /// - /// The named of the parameter that caused the exception. - public ArgumentNullOrEmptyException(string paramName) - : base(paramName) - { } - - /// - /// Initializes a new instance of the class with a specified error message and the name of the parameter that caused this exception. - /// - /// The named of the parameter that caused the exception. - /// A message that describes the error. - public ArgumentNullOrEmptyException(string paramName, string message) - : base(paramName, message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public ArgumentNullOrEmptyException(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized object data. - /// An object that describes the source or destination of the serialized data. - protected ArgumentNullOrEmptyException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } -} diff --git a/src/Umbraco.Abstractions/Exceptions/WontImplementException.cs b/src/Umbraco.Abstractions/Exceptions/WontImplementException.cs deleted file mode 100644 index e354bc4c3d..0000000000 --- a/src/Umbraco.Abstractions/Exceptions/WontImplementException.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Exceptions -{ - /// - /// The exception that is thrown when a requested method or operation is not, and will not be, implemented. - /// - /// - /// The is to be used when some code is not implemented, - /// but should eventually be implemented (i.e. work in progress) and is reported by tools such as ReSharper. - /// This exception is to be used when some code is not implemented, and is not meant to be, for whatever - /// reason. - /// - /// - [Serializable] - [Obsolete("If a method or operation is not, and will not be, implemented, it is invalid or not supported, so we should throw either an InvalidOperationException or NotSupportedException instead.")] - public class WontImplementException : NotImplementedException - { - /// - /// Initializes a new instance of the class. - /// - public WontImplementException() - { } - - /// - /// Initializes a new instance of the class with a specified reason message. - /// - /// The error message that explains the reason for the exception. - public WontImplementException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. If the parameter is not , the current exception is raised in a block that handles the inner exception. - public WontImplementException(string message, Exception inner) - : base(message, inner) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected WontImplementException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } -} diff --git a/src/Umbraco.Abstractions/IO/FileSecurityException.cs b/src/Umbraco.Abstractions/IO/FileSecurityException.cs deleted file mode 100644 index 8ce9ab34a5..0000000000 --- a/src/Umbraco.Abstractions/IO/FileSecurityException.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.IO -{ - /// - /// The exception that is thrown when the caller does not have the required permission to access a file. - /// - /// - [Obsolete("Throw an UnauthorizedAccessException instead.")] - [Serializable] - public class FileSecurityException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public FileSecurityException() - { } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public FileSecurityException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public FileSecurityException(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected FileSecurityException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } -} diff --git a/src/Umbraco.Abstractions/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Abstractions/Models/ContentEditing/ContentPropertyBasic.cs index c5c22484ad..2b70a63035 100644 --- a/src/Umbraco.Abstractions/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Abstractions/Models/ContentEditing/ContentPropertyBasic.cs @@ -53,11 +53,21 @@ namespace Umbraco.Web.Models.ContentEditing [ReadOnly(true)] public string Culture { get; set; } + /// + /// The segment of the property + /// + /// + /// The segment value of a property can always be null but can only have a non-null value + /// when the property can be varied by segment. + /// + [DataMember(Name = "segment")] + [ReadOnly(true)] + public string Segment { get; set; } + /// /// Used internally during model mapping /// [IgnoreDataMember] internal IDataEditor PropertyEditor { get; set; } - } } diff --git a/src/Umbraco.Abstractions/Models/ContentEditing/ContentVariantSave.cs b/src/Umbraco.Abstractions/Models/ContentEditing/ContentVariantSave.cs index deadac949a..9a7555ad92 100644 --- a/src/Umbraco.Abstractions/Models/ContentEditing/ContentVariantSave.cs +++ b/src/Umbraco.Abstractions/Models/ContentEditing/ContentVariantSave.cs @@ -29,6 +29,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "culture")] public string Culture { get; set; } + /// + /// The segment of this variant, if this is invariant than this is null or empty + /// + [DataMember(Name = "segment")] + public string Segment { get; set; } + /// /// Indicates if the variant should be updated /// diff --git a/src/Umbraco.Abstractions/PublishedModelFactoryExtensions.cs b/src/Umbraco.Abstractions/PublishedModelFactoryExtensions.cs index ac8c9f1be7..29ffee8ed4 100644 --- a/src/Umbraco.Abstractions/PublishedModelFactoryExtensions.cs +++ b/src/Umbraco.Abstractions/PublishedModelFactoryExtensions.cs @@ -17,25 +17,6 @@ namespace Umbraco.Core /// public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory; - [Obsolete("This method is no longer used or necessary and will be removed from future")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action) - { - if (factory is ILivePublishedModelFactory liveFactory) - { - lock (liveFactory.SyncRoot) - { - //Call refresh on the live factory to re-compile the models - liveFactory.Refresh(); - action(); - } - } - else - { - action(); - } - } - /// /// Sets a flag to reset the ModelsBuilder models if the is /// diff --git a/src/Umbraco.Abstractions/Services/IContentService.cs b/src/Umbraco.Abstractions/Services/IContentService.cs index 2a34fcb898..25428f420b 100644 --- a/src/Umbraco.Abstractions/Services/IContentService.cs +++ b/src/Umbraco.Abstractions/Services/IContentService.cs @@ -312,13 +312,6 @@ namespace Umbraco.Core.Services /// OperationResult MoveToRecycleBin(IContent content, int userId = Constants.Security.SuperUserId); - /// - /// Empties the recycle bin. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] - OperationResult EmptyRecycleBin(); - /// /// Empties the Recycle Bin by deleting all that resides in the bin /// diff --git a/src/Umbraco.Abstractions/Services/IMediaService.cs b/src/Umbraco.Abstractions/Services/IMediaService.cs index 6de2765644..c1537adbc8 100644 --- a/src/Umbraco.Abstractions/Services/IMediaService.cs +++ b/src/Umbraco.Abstractions/Services/IMediaService.cs @@ -156,13 +156,6 @@ namespace Umbraco.Core.Services /// Id of the User deleting the Media Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId); - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] - OperationResult EmptyRecycleBin(); - /// /// Empties the Recycle Bin by deleting all that resides in the bin /// diff --git a/src/Umbraco.Configuration/GlobalSettings.cs b/src/Umbraco.Configuration/GlobalSettings.cs index 499ff787a8..8376082c2a 100644 --- a/src/Umbraco.Configuration/GlobalSettings.cs +++ b/src/Umbraco.Configuration/GlobalSettings.cs @@ -185,23 +185,6 @@ namespace Umbraco.Core.Configuration } } - /// - /// Gets the name of the content XML file. - /// - /// The content XML. - /// - /// Defaults to ~/App_Data/umbraco.config - /// - public string ContentXmlFile - { - get - { - return ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ContentXML) - ? ConfigurationManager.AppSettings[Constants.AppSettings.ContentXML] - : "~/App_Data/umbraco.config"; - } - } - /// /// Gets the path to umbraco's root directory (/umbraco by default). /// diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index ea3ce83f5a..a3adcb56b7 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -49,9 +49,9 @@ - + - 1.0.0-beta2-19324-01 + 1.0.0-beta2-19554-01 runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/DeleteIndexBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/DeleteIndexBuilder.cs index b3cacb2f95..4aced4378c 100644 --- a/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/DeleteIndexBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/DeleteIndexBuilder.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Migrations.Expressions.Delete.Index { public class DeleteIndexBuilder : ExpressionBuilderBase, - IDeleteIndexForTableBuilder, IDeleteIndexOnColumnBuilder + IDeleteIndexForTableBuilder, IExecutableBuilder { public DeleteIndexBuilder(DeleteIndexExpression expression) : base(expression) @@ -14,28 +14,10 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.Index /// public void Do() => Expression.Execute(); - public IndexColumnDefinition CurrentColumn { get; set; } - - public IDeleteIndexOnColumnBuilder OnTable(string tableName) + public IExecutableBuilder OnTable(string tableName) { Expression.Index.TableName = tableName; return this; } - - /// - public IExecutableBuilder OnColumn(string columnName) - { - var column = new IndexColumnDefinition { Name = columnName }; - Expression.Index.Columns.Add(column); - return new ExecutableBuilder(Expression); - } - - /// - public IExecutableBuilder OnColumns(params string[] columnNames) - { - foreach (string columnName in columnNames) - Expression.Index.Columns.Add(new IndexColumnDefinition { Name = columnName }); - return new ExecutableBuilder(Expression); - } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/IDeleteIndexForTableBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/IDeleteIndexForTableBuilder.cs index 8501c6b02f..8251107cbb 100644 --- a/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/IDeleteIndexForTableBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/IDeleteIndexForTableBuilder.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Migrations.Expressions.Delete.Index +using Umbraco.Core.Migrations.Expressions.Common; + +namespace Umbraco.Core.Migrations.Expressions.Delete.Index { /// /// Builds a Delete expression. @@ -8,6 +10,6 @@ /// /// Specifies the table of the index to delete. /// - IDeleteIndexOnColumnBuilder OnTable(string tableName); + IExecutableBuilder OnTable(string tableName); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/IDeleteIndexOnColumnBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/IDeleteIndexOnColumnBuilder.cs deleted file mode 100644 index 3aa877bf8e..0000000000 --- a/src/Umbraco.Infrastructure/Migrations/Expressions/Delete/Index/IDeleteIndexOnColumnBuilder.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Umbraco.Core.Migrations.Expressions.Common; - -namespace Umbraco.Core.Migrations.Expressions.Delete.Index -{ - /// - /// Builds a Delete expression. - /// - public interface IDeleteIndexOnColumnBuilder : IFluentBuilder, IExecutableBuilder - { - /// - /// Specifies the column of the index. - /// - [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] - IExecutableBuilder OnColumn(string columnName); - - /// - /// Specifies the column of the index. - /// - [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")] - IExecutableBuilder OnColumns(params string[] columnNames); - } -} diff --git a/src/Umbraco.Infrastructure/Models/BackOfficeTour.cs b/src/Umbraco.Infrastructure/Models/BackOfficeTour.cs index d5987ec5bc..7391765193 100644 --- a/src/Umbraco.Infrastructure/Models/BackOfficeTour.cs +++ b/src/Umbraco.Infrastructure/Models/BackOfficeTour.cs @@ -16,16 +16,25 @@ namespace Umbraco.Web.Models [DataMember(Name = "name")] public string Name { get; set; } + [DataMember(Name = "alias")] public string Alias { get; set; } + [DataMember(Name = "group")] public string Group { get; set; } + [DataMember(Name = "groupOrder")] public int GroupOrder { get; set; } + + [DataMember(Name = "hidden")] + public bool Hidden { get; set; } + [DataMember(Name = "allowDisable")] public bool AllowDisable { get; set; } + [DataMember(Name = "requiredSections")] public List RequiredSections { get; set; } + [DataMember(Name = "steps")] public BackOfficeTourStep[] Steps { get; set; } diff --git a/src/Umbraco.Infrastructure/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Infrastructure/Models/Mapping/ContentVariantMapper.cs index 1a911f6d1e..10a19287e7 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/ContentVariantMapper.cs @@ -21,52 +21,117 @@ namespace Umbraco.Web.Models.Mapping public IEnumerable Map(IContent source, MapperContext context) { - var result = new List(); - if (!source.ContentType.VariesByCulture()) + var variesByCulture = source.ContentType.VariesByCulture(); + var variesBySegment = source.ContentType.VariesBySegment(); + + IList variants = new List(); + + if (!variesByCulture && !variesBySegment) { - //this is invariant so just map the IContent instance to ContentVariationDisplay - result.Add(context.Map(source)); + // this is invariant so just map the IContent instance to ContentVariationDisplay + var variantDisplay = context.Map(source); + variants.Add(variantDisplay); + } + else if (variesByCulture && !variesBySegment) + { + var languages = GetLanguages(context); + variants = languages + .Select(language => CreateVariantDisplay(context, source, language, null)) + .ToList(); + } + else if (variesBySegment && !variesByCulture) + { + // Segment only + var segments = GetSegments(source); + variants = segments + .Select(segment => CreateVariantDisplay(context, source, null, segment)) + .ToList(); } else { - var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); - if (allLanguages.Count == 0) return Enumerable.Empty(); //this should never happen + // Culture and segment + var languages = GetLanguages(context).ToList(); + var segments = GetSegments(source).ToList(); - var langs = context.MapEnumerable(allLanguages).ToList(); - - //create a variant for each language, then we'll populate the values - var variants = langs.Select(x => + if (languages.Count == 0 || segments.Count == 0) { - //We need to set the culture in the mapping context since this is needed to ensure that the correct property values - //are resolved during the mapping - context.SetCulture(x.IsoCode); - return context.Map(source); - }).ToList(); - - for (int i = 0; i < langs.Count; i++) - { - var x = langs[i]; - var variant = variants[i]; - - variant.Language = x; - variant.Name = source.GetCultureName(x.IsoCode); + // This should not happen + throw new InvalidOperationException("No languages or segments available"); } - //Put the default language first in the list & then sort rest by a-z - var defaultLang = variants.SingleOrDefault(x => x.Language.IsDefault); + variants = languages + .SelectMany(language => segments + .Select(segment => CreateVariantDisplay(context, source, language, segment))) + .ToList(); + } - //Remove the default language from the list for now - variants.Remove(defaultLang); - - //Sort the remaining languages a-z - variants = variants.OrderBy(x => x.Language.Name).ToList(); - - //Insert the default language as the first item - variants.Insert(0, defaultLang); + return SortVariants(variants); + } + private IList SortVariants(IList variants) + { + if (variants == null || variants.Count <= 1) + { return variants; } - return result; + + // Default variant first, then order by language, segment. + return variants + .OrderBy(v => IsDefaultLanguage(v) ? 0 : 1) + .ThenBy(v => IsDefaultSegment(v) ? 0 : 1) + .ThenBy(v => v?.Language?.Name) + .ThenBy(v => v.Segment) + .ToList(); + } + + private static bool IsDefaultSegment(ContentVariantDisplay variant) + { + return variant.Segment == null; + } + + private static bool IsDefaultLanguage(ContentVariantDisplay variant) + { + return variant.Language == null || variant.Language.IsDefault; + } + + private IEnumerable GetLanguages(MapperContext context) + { + var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); + if (allLanguages.Count == 0) + { + // This should never happen + return Enumerable.Empty(); + } + else + { + return context.MapEnumerable(allLanguages).ToList(); + } + } + + /// + /// Returns all segments assigned to the content + /// + /// + /// + /// Returns all segments assigned to the content including 'null' values + /// + private IEnumerable GetSegments(IContent content) + { + return content.Properties.SelectMany(p => p.Values.Select(v => v.Segment)).Distinct(); + } + + private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, Language language, string segment) + { + context.SetCulture(language?.IsoCode); + context.SetSegment(segment); + + var variantDisplay = context.Map(content); + + variantDisplay.Segment = segment; + variantDisplay.Language = language; + variantDisplay.Name = content.GetCultureName(language?.IsoCode); + + return variantDisplay; } } } diff --git a/src/Umbraco.Infrastructure/Models/Mapping/MacroMapDefinition.cs b/src/Umbraco.Infrastructure/Models/Mapping/MacroMapDefinition.cs index 7138c5d795..d980958a91 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/MacroMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/MacroMapDefinition.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Logging; @@ -23,6 +24,7 @@ namespace Umbraco.Web.Models.Mapping public void DefineMaps(UmbracoMapper mapper) { mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new MacroDisplay(), Map); mapper.Define>((source, context) => context.MapEnumerable(source.Properties.Values)); mapper.Define((source, context) => new MacroParameter(), Map); } @@ -40,6 +42,23 @@ namespace Umbraco.Web.Models.Mapping target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key); } + private void Map(IMacro source, MacroDisplay target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = Constants.Icons.Macro; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = "-1," + source.Id; + target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key); + target.CacheByPage = source.CacheByPage; + target.CacheByUser = source.CacheByMember; + target.CachePeriod = source.CacheDuration; + target.UseInEditor = source.UseInEditor; + target.RenderInEditor = !source.DontRender; + target.View = source.MacroSource; + } // Umbraco.Code.MapAll -Value private void Map(IMacroProperty source, MacroParameter target, MapperContext context) { diff --git a/src/Umbraco.Infrastructure/Models/Mapping/MapperContextExtensions.cs b/src/Umbraco.Infrastructure/Models/Mapping/MapperContextExtensions.cs index 020a66cd2a..3133441846 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/MapperContextExtensions.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/MapperContextExtensions.cs @@ -8,6 +8,7 @@ namespace Umbraco.Web.Models.Mapping public static class MapperContextExtensions { private const string CultureKey = "Map.Culture"; + private const string SegmentKey = "Map.Segment"; private const string IncludedPropertiesKey = "Map.IncludedProperties"; /// @@ -18,6 +19,14 @@ namespace Umbraco.Web.Models.Mapping return context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null; } + /// + /// Gets the context segment. + /// + public static string GetSegment(this MapperContext context) + { + return context.HasItems && context.Items.TryGetValue(SegmentKey, out var obj) && obj is string s ? s : null; + } + /// /// Sets a context culture. /// @@ -26,6 +35,14 @@ namespace Umbraco.Web.Models.Mapping context.Items[CultureKey] = culture; } + /// + /// Sets a context segment. + /// + public static void SetSegment(this MapperContext context, string segment) + { + context.Items[SegmentKey] = segment; + } + /// /// Get included properties. /// diff --git a/src/Umbraco.Infrastructure/Persistence/EntityNotFoundException.cs b/src/Umbraco.Infrastructure/Persistence/EntityNotFoundException.cs deleted file mode 100644 index ea6d5142f0..0000000000 --- a/src/Umbraco.Infrastructure/Persistence/EntityNotFoundException.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace Umbraco.Core.Persistence -{ - /// - /// An exception used to indicate that an Umbraco entity could not be found. - /// - /// - [Obsolete("Instead of throwing an exception, return null or an HTTP 404 status code instead.")] - [Serializable] - public class EntityNotFoundException : Exception - { - /// - /// Gets the identifier. - /// - /// - /// The identifier. - /// - /// - /// This object should be serializable to prevent a to be thrown. - /// - public object Id { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - public EntityNotFoundException() - { } - - /// - /// Initializes a new instance of the class. - /// - /// The identifier. - /// The message. - public EntityNotFoundException(object id, string message) - : base(message) - { - Id = id; - } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public EntityNotFoundException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public EntityNotFoundException(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected EntityNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Id = info.GetValue(nameof(Id), typeof(object)); - } - - /// - /// When overridden in a derived class, sets the with information about the exception. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// info - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - info.AddValue(nameof(Id), Id); - - base.GetObjectData(info, context); - } - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() - { - var result = base.ToString(); - - if (Id != null) - { - return "Umbraco entity (id: " + Id + ") not found. " + result; - } - - return result; - } - } -} diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index f386f22f39..d02f84d294 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs @@ -2030,13 +2030,6 @@ namespace Umbraco.Core.Services.Implement _documentRepository.Save(content); } - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] - public OperationResult EmptyRecycleBin() => EmptyRecycleBin(Constants.Security.SuperUserId); - /// /// Empties the Recycle Bin by deleting all that resides in the bin /// diff --git a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs index 27e428d1d6..14bebc4eb8 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs @@ -1029,13 +1029,6 @@ namespace Umbraco.Core.Services.Implement _mediaRepository.Save(media); } - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] - public OperationResult EmptyRecycleBin() => EmptyRecycleBin(Constants.Security.SuperUserId); - /// /// Empties the Recycle Bin by deleting all that resides in the bin /// diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index 126a6dbc40..d5138953cf 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -23,6 +23,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose { var isLegacyModelsBuilderInstalled = IsLegacyModelsBuilderInstalled(); + + composition.Configs.Add(() => new ModelsBuilderConfig(composition.IOHelper)); + if (isLegacyModelsBuilderInstalled) { ComposeForLegacyModelsBuilder(composition); @@ -32,7 +35,6 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose composition.Components().Append(); composition.Register(Lifetime.Singleton); - composition.Configs.Add(() => new ModelsBuilderConfig(composition.IOHelper)); composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index 698f00c94a..d0137ed2b2 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -1,9 +1,9 @@ using System; using System.Configuration; using System.IO; +using System.Threading; using System.Web.Configuration; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.IO; namespace Umbraco.ModelsBuilder.Embedded.Configuration @@ -14,6 +14,13 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration public class ModelsBuilderConfig : IModelsBuilderConfig { private readonly IIOHelper _ioHelper; + private const string Prefix = "Umbraco.ModelsBuilder."; + private object _modelsModelLock; + private bool _modelsModelConfigured; + private ModelsMode _modelsMode; + private object _flagOutOfDateModelsLock; + private bool _flagOutOfDateModelsConfigured; + private bool _flagOutOfDateModels; public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; public string DefaultModelsDirectory => _ioHelper.MapPath("~/App_Data/Models"); @@ -24,11 +31,10 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration public ModelsBuilderConfig(IIOHelper ioHelper) { _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); - const string prefix = "Umbraco.ModelsBuilder."; // giant kill switch, default: false // must be explicitely set to true for anything else to happen - Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; + Enable = ConfigurationManager.AppSettings[Prefix + "Enable"] == "true"; // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; @@ -38,44 +44,19 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration // stop here, everything is false if (!Enable) return; - // mode - var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; - if (!string.IsNullOrWhiteSpace(modelsMode)) - { - switch (modelsMode) - { - case nameof(ModelsMode.Nothing): - ModelsMode = ModelsMode.Nothing; - break; - case nameof(ModelsMode.PureLive): - ModelsMode = ModelsMode.PureLive; - break; - case nameof(ModelsMode.AppData): - ModelsMode = ModelsMode.AppData; - break; - case nameof(ModelsMode.LiveAppData): - ModelsMode = ModelsMode.LiveAppData; - break; - default: - throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." - + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); - } - } - // default: false - AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); + AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[Prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); // default: true - EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false"); - FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + EnableFactory = !ConfigurationManager.AppSettings[Prefix + "EnableFactory"].InvariantEquals("false"); // default: initialized above with DefaultModelsNamespace const - var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; + var value = ConfigurationManager.AppSettings[Prefix + "ModelsNamespace"]; if (!string.IsNullOrWhiteSpace(value)) ModelsNamespace = value; // default: initialized above with DefaultModelsDirectory const - value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; + value = ConfigurationManager.AppSettings[Prefix + "ModelsDirectory"]; if (!string.IsNullOrWhiteSpace(value)) { var root = _ioHelper.MapPath("~/"); @@ -87,18 +68,14 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration } // default: 0 - value = ConfigurationManager.AppSettings[prefix + "DebugLevel"]; + value = ConfigurationManager.AppSettings[Prefix + "DebugLevel"]; if (!string.IsNullOrWhiteSpace(value)) { - int debugLevel; - if (!int.TryParse(value, out debugLevel)) + if (!int.TryParse(value, out var debugLevel)) throw new ConfigurationErrorsException($"Invalid debug level \"{value}\"."); DebugLevel = debugLevel; } - // not flagging if not generating, or live (incl. pure) - if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) - FlagOutOfDateModels = false; } /// @@ -116,11 +93,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration { _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); Enable = enable; - ModelsMode = modelsMode; + _modelsMode = modelsMode; ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; EnableFactory = enableFactory; - FlagOutOfDateModels = flagOutOfDateModels; + _flagOutOfDateModels = flagOutOfDateModels; ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory; AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory; DebugLevel = debugLevel; @@ -169,7 +146,26 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// /// Gets the models mode. /// - public ModelsMode ModelsMode { get; } + public ModelsMode ModelsMode => + LazyInitializer.EnsureInitialized(ref _modelsMode, ref _modelsModelConfigured, ref _modelsModelLock, () => + { + // mode + var modelsMode = ConfigurationManager.AppSettings[Prefix + "ModelsMode"]; + if (string.IsNullOrWhiteSpace(modelsMode)) return ModelsMode.Nothing; //default + switch (modelsMode) + { + case nameof(ModelsMode.Nothing): + return ModelsMode.Nothing; + case nameof(ModelsMode.PureLive): + return ModelsMode.PureLive; + case nameof(ModelsMode.AppData): + return ModelsMode.AppData; + case nameof(ModelsMode.LiveAppData): + return ModelsMode.LiveAppData; + default: + throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); + } + }); /// /// Gets a value indicating whether system.web/compilation/@debug is true. @@ -201,7 +197,17 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// Models become out-of-date when data types or content types are updated. When this /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are /// generated through the dashboard, the files is cleared. Default value is false. - public bool FlagOutOfDateModels { get; } + public bool FlagOutOfDateModels + => LazyInitializer.EnsureInitialized(ref _flagOutOfDateModels, ref _flagOutOfDateModelsConfigured, ref _flagOutOfDateModelsLock, () => + { + var flagOutOfDateModels = !ConfigurationManager.AppSettings[Prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) + { + flagOutOfDateModels = false; + } + + return flagOutOfDateModels; + }); /// /// Gets the models directory. diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index d1eab15350..073a6566d0 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -92,7 +92,7 @@ 2.10.0 - 1.0.0-beta2-19324-01 + 1.0.0-beta2-19554-01 runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Umbraco.TestData/Properties/AssemblyInfo.cs b/src/Umbraco.TestData/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3c4251cdf6 --- /dev/null +++ b/src/Umbraco.TestData/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Umbraco.TestData")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Umbraco.TestData")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fb5676ed-7a69-492c-b802-e7b24144c0fc")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Umbraco.TestData/SegmentTestController.cs b/src/Umbraco.TestData/SegmentTestController.cs new file mode 100644 index 0000000000..33badbbb55 --- /dev/null +++ b/src/Umbraco.TestData/SegmentTestController.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Mvc; + +namespace Umbraco.TestData +{ + public class SegmentTestController : SurfaceController + { + + public ActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias) + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + var ct = Services.ContentTypeService.Get(alias); + if (ct == null) + return Content($"No document type found by alias {alias}"); + + var propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); + if (propType == null) + return Content($"The document type {alias} does not have a property type {propertyTypeAlias ?? "null"}"); + + if (ct.Variations.VariesBySegment()) + return Content($"The document type {alias} already allows segments, nothing has been changed"); + + ct.Variations = ct.Variations.SetFlag(ContentVariation.Segment); + + propType.Variations = propType.Variations.SetFlag(ContentVariation.Segment); + + Services.ContentTypeService.Save(ct); + return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments"); + } + + public ActionResult DisableDocTypeSegments(string alias) + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + var ct = Services.ContentTypeService.Get(alias); + if (ct == null) + return Content($"No document type found by alias {alias}"); + + if (!ct.VariesBySegment()) + return Content($"The document type {alias} does not allow segments, nothing has been changed"); + + ct.Variations = ct.Variations.UnsetFlag(ContentVariation.Segment); + + Services.ContentTypeService.Save(ct); + return Content($"The document type {alias} no longer allows segments"); + } + + public ActionResult AddSegmentData(int contentId, string propertyAlias, string value, string segment, string culture = null) + { + var content = Services.ContentService.GetById(contentId); + if (content == null) + return Content($"No content found by id {contentId}"); + + if (propertyAlias.IsNullOrWhiteSpace() || !content.HasProperty(propertyAlias)) + return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias ?? "null"}"); + + if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace()) + return Content($"The content by id {contentId} varies by culture but no culture was specified"); + + if (value.IsNullOrWhiteSpace()) + return Content("'value' cannot be null"); + + if (segment.IsNullOrWhiteSpace()) + return Content("'segment' cannot be null"); + + content.SetValue(propertyAlias, value, culture, segment); + Services.ContentService.Save(content); + + return Content($"Segment value has been set on content {contentId} for property {propertyAlias}"); + } + } +} diff --git a/src/Umbraco.TestData/Umbraco.TestData.csproj b/src/Umbraco.TestData/Umbraco.TestData.csproj new file mode 100644 index 0000000000..a79a417b33 --- /dev/null +++ b/src/Umbraco.TestData/Umbraco.TestData.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC} + Library + Properties + Umbraco.TestData + Umbraco.TestData + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + {29aa69d9-b597-4395-8d42-43b1263c240a} + Umbraco.Abstractions + + + {3ae7bf57-966b-45a5-910a-954d7c554441} + Umbraco.Infrastructure + + + {651e1350-91b6-44b7-bd60-7207006d7003} + Umbraco.Web + + + + + 28.4.4 + + + 5.2.7 + + + + \ No newline at end of file diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs new file mode 100644 index 0000000000..522d4f6a40 --- /dev/null +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using System.Web.Mvc; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web; +using Umbraco.Web.Mvc; +using System.Configuration; +using Bogus; +using Umbraco.Core.Scoping; +using Umbraco.Core.Strings; + +namespace Umbraco.TestData +{ + /// + /// Creates test data + /// + public class UmbracoTestDataController : SurfaceController + { + private const string RichTextDataTypeName = "UmbracoTestDataContent.RTE"; + private const string MediaPickerDataTypeName = "UmbracoTestDataContent.MediaPicker"; + private const string TextDataTypeName = "UmbracoTestDataContent.Text"; + private const string TestDataContentTypeAlias = "umbTestDataContent"; + private readonly IScopeProvider _scopeProvider; + private readonly PropertyEditorCollection _propertyEditors; + private readonly IShortStringHelper _shortStringHelper; + + public UmbracoTestDataController(IScopeProvider scopeProvider, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger, umbracoHelper) + { + _scopeProvider = scopeProvider; + _propertyEditors = propertyEditors; + _shortStringHelper = shortStringHelper; + } + + /// + /// Creates a content and associated media tree (hierarchy) + /// + /// + /// + /// + /// + /// + /// Each content item created is associated to a media item via a media picker and therefore a relation is created between the two + /// + public ActionResult CreateTree(int count, int depth, string locale = "en") + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return HttpNotFound(); + + if (!Validate(count, depth, out var message, out var perLevel)) + throw new InvalidOperationException(message); + + var faker = new Faker(locale); + var company = faker.Company.CompanyName(); + + using (var scope = _scopeProvider.CreateScope()) + { + var imageIds = CreateMediaTree(company, faker, count, depth).ToList(); + var contentIds = CreateContentTree(company, faker, count, depth, imageIds, out var root).ToList(); + + Services.ContentService.SaveAndPublishBranch(root, true); + + scope.Complete(); + } + + + return Content("Done"); + } + + private bool Validate(int count, int depth, out string message, out int perLevel) + { + perLevel = 0; + message = null; + + if (count <= 0) + { + message = "Count must be more than 0"; + return false; + } + + perLevel = count / depth; + if (perLevel < 1) + { + message = "Count not high enough for specified for number of levels required"; + return false; + } + + return true; + } + + /// + /// Utility to create a tree hierarchy + /// + /// + /// + /// + /// + /// + /// A callback that returns a tuple of Content and another callback to produce a Container. + /// For media, a container will be another folder, for content the container will be the Content itself. + /// + /// + private IEnumerable CreateHierarchy( + T parent, int count, int depth, + Func container)> create) + where T: class, IContentBase + { + yield return parent.GetUdi(); + + // This will not calculate a balanced tree but it will ensure that there will be enough nodes deep enough to not fill up the tree. + var totalDescendants = count - 1; + var perLevel = Math.Ceiling(totalDescendants / (double)depth); + var perBranch = Math.Ceiling(perLevel / depth); + + var tracked = new Stack<(T parent, int childCount)>(); + + var currChildCount = 0; + + for (int i = 0; i < count; i++) + { + var created = create(parent); + var contentItem = created.content; + + yield return contentItem.GetUdi(); + + currChildCount++; + + if (currChildCount == perBranch) + { + // move back up... + + var prev = tracked.Pop(); + + // restore child count + currChildCount = prev.childCount; + // restore the parent + parent = prev.parent; + + } + else if (contentItem.Level < depth) + { + // track the current parent and it's current child count + tracked.Push((parent, currChildCount)); + + // not at max depth, create below + parent = created.container(); + + currChildCount = 0; + } + + } + } + + /// + /// Creates the media tree hiearachy + /// + /// + /// + /// + /// + /// + private IEnumerable CreateMediaTree(string company, Faker faker, int count, int depth) + { + var parent = Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder); + + return CreateHierarchy(parent, count, depth, currParent => + { + var imageUrl = faker.Image.PicsumUrl(); + + // we are appending a &ext=.jpg to the end of this for a reason. The result of this url will be something like: + // https://picsum.photos/640/480/?image=106 + // and due to the way that we detect images there must be an extension so we'll change it to + // https://picsum.photos/640/480/?image=106&ext=.jpg + // which will trick our app into parsing this and thinking it's an image ... which it is so that's good. + // if we don't do this we don't get thumbnails in the back office. + imageUrl += "&ext=.jpg"; + + var media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image); + media.SetValue(Constants.Conventions.Media.File, imageUrl); + Services.MediaService.Save(media); + return (media, () => + { + // create a folder to contain child media + var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder); + return container; + }); + }); + } + + /// + /// Creates the content tree hiearachy + /// + /// + /// + /// + /// + /// + /// + private IEnumerable CreateContentTree(string company, Faker faker, int count, int depth, List imageIds, out IContent root) + { + var random = new Random(company.GetHashCode()); + + var docType = GetOrCreateContentType(); + + var parent = Services.ContentService.Create(company, -1, docType.Alias); + parent.SetValue("review", faker.Rant.Review()); + parent.SetValue("desc", company); + parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); + Services.ContentService.Save(parent); + + root = parent; + + return CreateHierarchy(parent, count, depth, currParent => + { + var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias); + content.SetValue("review", faker.Rant.Review()); + content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); ; + content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); + + Services.ContentService.Save(content); + return (content, () => content); + }); + + } + + private IContentType GetOrCreateContentType() + { + var docType = Services.ContentTypeService.Get(TestDataContentTypeAlias); + if (docType != null) + return docType; + + docType = new ContentType(_shortStringHelper, -1) + { + Alias = TestDataContentTypeAlias, + Name = "Umbraco Test Data Content", + Icon = "icon-science color-green" + }; + docType.AddPropertyGroup("Content"); + docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateRichText(), "review") + { + Name = "Review" + }); + docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateMediaPicker(), "media") + { + Name = "Media" + }); + docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateText(), "desc") + { + Name = "Description" + }); + Services.ContentTypeService.Save(docType); + docType.AllowedContentTypes = new[] { new ContentTypeSort(docType.Id, 0) }; + Services.ContentTypeService.Save(docType); + return docType; + } + + private IDataType GetOrCreateRichText() => GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce); + + private IDataType GetOrCreateMediaPicker() => GetOrCreateDataType(MediaPickerDataTypeName, Constants.PropertyEditors.Aliases.MediaPicker); + + private IDataType GetOrCreateText() => GetOrCreateDataType(TextDataTypeName, Constants.PropertyEditors.Aliases.TextBox); + + private IDataType GetOrCreateDataType(string name, string editorAlias) + { + var dt = Services.DataTypeService.GetDataType(name); + if (dt != null) return dt; + + var editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias); + if (editor == null) + throw new InvalidOperationException($"No {editorAlias} editor found"); + + dt = new DataType(editor) + { + Name = name, + Configuration = editor.GetConfigurationEditor().DefaultConfigurationObject, + DatabaseType = ValueStorageType.Ntext + }; + + Services.DataTypeService.Save(dt); + return dt; + } + } +} diff --git a/src/Umbraco.TestData/readme.md b/src/Umbraco.TestData/readme.md new file mode 100644 index 0000000000..f943326303 --- /dev/null +++ b/src/Umbraco.TestData/readme.md @@ -0,0 +1,51 @@ +## Umbraco Test Data + +This project is a utility to be able to generate large amounts of content and media in an +Umbraco installation for testing. + +Currently this project is referenced in the Umbraco.Web.UI project but only when it's being built +in Debug mode (i.e. when testing within Visual Studio). + +## Usage + +You must use SQL Server for this, using SQLCE will die if you try to bulk create huge amounts of data. + +It has to be enabled by an appSetting: + +```xml + +``` + +Once this is enabled this endpoint can be executed: + +`/umbraco/surface/umbracotestdata/CreateTree?count=100&depth=5` + +The query string options are: + +* `count` = the number of content and media nodes to create +* `depth` = how deep the trees created will be +* `locale` (optional, default = "en") = the language that the data will be generated in + +This creates a content and associated media tree (hierarchy). Each content item created is associated +to a media item via a media picker and therefore a relation is created between the two. Each content and +media tree created have the same root node name so it's easy to know which content branch relates to +which media branch. + +All values are generated using the very handy `Bogus` package. + +## Schema + +This will install some schema items: + +* `umbTestDataContent` Document Type. __TIP__: If you want to delete all of the content data generated with this tool, just delete this content type +* `UmbracoTestDataContent.RTE` Data Type +* `UmbracoTestDataContent.MediaPicker` Data Type +* `UmbracoTestDataContent.Text` Data Type + +For media, the normal folder and image is used + +## Media + +This does not upload physical files, it just uses a randomized online image as the `umbracoFile` value. +This works when viewing the media item in the media section and the image will show up and with recent changes this will also work +when editing content to view the thumbnail for the picked media. diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index e1c3ecc891..56d9d46e7d 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -251,7 +251,7 @@ namespace Umbraco.Web.PublishedModels { Alias = "prop3", ClrName = "Prop3", - ModelClrType = typeof(global::Umbraco.Core.IO.FileSecurityException), + ModelClrType = typeof(global::Umbraco.Core.Exceptions.BootFailedException), }); var types = new[] { type1 }; @@ -272,7 +272,7 @@ namespace Umbraco.Web.PublishedModels Assert.IsTrue(gen.Contains(" global::Umbraco.Core.Models.PublishedContent.IPublishedContent Prop1")); Assert.IsTrue(gen.Contains(" global::System.Text.StringBuilder Prop2")); - Assert.IsTrue(gen.Contains(" global::Umbraco.Core.IO.FileSecurityException Prop3")); + Assert.IsTrue(gen.Contains(" global::Umbraco.Core.Exceptions.BootFailedException Prop3")); } [TestCase("int", typeof(int))] diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index a87e3cff8f..db5e59c9c0 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -7,6 +7,8 @@ using Umbraco.Web; using Umbraco.Core; using Umbraco.Tests.Testing; using Umbraco.Web.Composing; +using Moq; +using Examine; namespace Umbraco.Tests.PublishedContent { @@ -201,7 +203,8 @@ namespace Umbraco.Tests.PublishedContent [Test] public void PublishedContentQueryTypedContentList() { - var query = new PublishedContentQuery(Current.UmbracoContext.PublishedSnapshot, Current.UmbracoContext.VariationContextAccessor); + var examineManager = new Mock(); + var query = new PublishedContentQuery(Current.UmbracoContext.PublishedSnapshot, Current.UmbracoContext.VariationContextAccessor, examineManager.Object); var result = query.Content(new[] { 1, 2, 4 }).ToArray(); Assert.AreEqual(2, result.Length); Assert.AreEqual(1, result[0].Id); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index e51d8e7a7a..fa47f085c9 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -2372,8 +2372,6 @@ namespace Umbraco.Tests.Services //MCH: I'm guessing this is an issue because of the format the date is actually stored as, right? Cause we don't do any formatting when saving or loading Assert.That(sut.GetValue("dateTime").ToString("G"), Is.EqualTo(content.GetValue("dateTime").ToString("G"))); Assert.That(sut.GetValue("colorPicker"), Is.EqualTo("black")); - //that one is gone in 7.4 - //Assert.That(sut.GetValue("folderBrowser"), Is.Null); Assert.That(sut.GetValue("ddlMultiple"), Is.EqualTo("1234,1235")); Assert.That(sut.GetValue("rbList"), Is.EqualTo("random")); Assert.That(sut.GetValue("date").ToString("G"), Is.EqualTo(content.GetValue("date").ToString("G"))); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs index 19a57d7775..dfb7834aff 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContent.cs @@ -122,8 +122,6 @@ namespace Umbraco.Tests.TestHelpers.Entities content.SetValue("label", "Non-editable label"); content.SetValue("dateTime", DateTime.Now.AddDays(-20)); content.SetValue("colorPicker", "black"); - //that one is gone in 7.4 - //content.SetValue("folderBrowser", ""); content.SetValue("ddlMultiple", "1234,1235"); content.SetValue("rbList", "random"); content.SetValue("date", DateTime.Now.AddDays(-10)); diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 52e86b36db..121748884a 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -115,7 +115,6 @@ namespace Umbraco.Tests.Testing.TestingTests internal class FakeUmbracoApiController : UmbracoApiController { - public FakeUmbracoApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { } public FakeUmbracoApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { } } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1729bd5c20..695ce49fce 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -78,7 +78,7 @@ - + 1.8.14 diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index d514f27345..75960a93cf 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -34,6 +34,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Umbraco.Web.Composing; using Task = System.Threading.Tasks.Task; +using Umbraco.Core.Mapping; namespace Umbraco.Tests.Web.Controllers { @@ -268,7 +269,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), helper, - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return controller; } @@ -303,7 +305,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), helper, - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return controller; } @@ -346,7 +349,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), helper, - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return controller; } @@ -394,7 +398,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), helper, - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return controller; } @@ -434,7 +439,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), helper, - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return controller; } @@ -480,7 +486,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), helper, - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return controller; } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 25cb767eaf..ccd9ff2063 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -34,6 +34,7 @@ using Umbraco.Web.Editors; using Umbraco.Web.Features; using Umbraco.Web.Models.ContentEditing; using IUser = Umbraco.Core.Models.Membership.IUser; +using Umbraco.Core.Mapping; namespace Umbraco.Tests.Web.Controllers { @@ -88,7 +89,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), helper, Factory.GetInstance(), - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return usersController; } @@ -154,7 +156,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), helper, Factory.GetInstance(), - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return usersController; } @@ -191,7 +194,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), helper, Factory.GetInstance(), - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return usersController; } @@ -263,7 +267,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), helper, Factory.GetInstance(), - ShortStringHelper); + ShortStringHelper, + Factory.GetInstance()); return usersController; } diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs index 04bd4f6e15..36ddecc676 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlStringUtilitiesTests.cs @@ -15,14 +15,6 @@ namespace Umbraco.Tests.Web.Mvc _htmlStringUtilities = new HtmlStringUtilities(); } - [Test] - public void ReplaceLineBreaksWithHtmlBreak() - { - var output = _htmlStringUtilities.ReplaceLineBreaksForHtml("

hello world

hello world\r\nhello world\rhello world\nhello world

").ToString(); - var expected = "

hello world

hello world
hello world
hello world
hello world

"; - Assert.AreEqual(expected, output); - } - [Test] public void TruncateWithElipsis() { diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less index 0b90a13059..5eb8b638a2 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/code.less @@ -9,14 +9,13 @@ pre.code { padding: 0 3px 2px; #font > #family > .monospace; font-size: @baseFontSize - 2; - color: @grayDark; + color: @blueExtraDark; .border-radius(3px); } // Inline code code { padding: 2px 4px; - color: #d14; background-color: #f7f7f9; border: 1px solid #e1e1e8; white-space: nowrap; diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less index 94f229a191..5d0e1c8e7e 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less @@ -79,11 +79,9 @@ // Hover/Focus state // ----------- .dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, .dropdown-menu > li > button:hover, -.dropdown-menu > li > button:focus, .dropdown-submenu:hover > a, -.dropdown-submenu:focus > a { +.dropdown-submenu:hover > button { text-decoration: none; color: @dropdownLinkColorHover; #gradient > .vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%)); @@ -92,8 +90,7 @@ // Active state // ------------ .dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { +.dropdown-menu > .active > a:hover { color: @dropdownLinkColorActive; text-decoration: none; outline: 0; @@ -104,13 +101,11 @@ // -------------- // Gray out text and ensure the hover/focus state remains gray .dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { +.dropdown-menu > .disabled > a:hover { color: @grayLight; } // Nuke hover/focus effects -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { +.dropdown-menu > .disabled > a:hover { text-decoration: none; background-color: transparent; background-image: none; // Remove CSS gradient diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js index 5d34ad2906..79cb99cf07 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbtoggle.directive.js @@ -75,6 +75,8 @@ scope.displayLabelOff = ""; function onInit() { + scope.inputId = scope.inputId || "umb-toggle_" + String.CreateGuid(); + setLabelText(); // must wait until the current digest cycle is finished before we emit this event on init, // otherwise other property editors might not yet be ready to receive the event diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js index ff51b1ae90..d562b21d52 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -46,6 +46,8 @@ vm.change = change; function onInit() { + vm.inputId = vm.inputId || "umb-check_" + String.CreateGuid(); + // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ if (vm.labelKey) { localizationService.localize(vm.labelKey).then(function (data) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js index f3ecac2a74..7ed84547f1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js @@ -44,6 +44,8 @@ vm.change = change; function onInit() { + vm.inputId = vm.inputId || "umb-radio_" + String.CreateGuid(); + // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ if (vm.labelKey) { localizationService.localize(vm.labelKey).then(function (data) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcodesnippet.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcodesnippet.directive.js new file mode 100644 index 0000000000..f0dad31ee2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcodesnippet.directive.js @@ -0,0 +1,119 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbCodeSnippet +@restrict E +@scope + +@description + +

Markup example

+
+	
+ + + {{code}} + + +
+
+ +

Controller example

+
+	(function () {
+		"use strict";
+
+		function Controller() {
+
+            var vm = this;
+
+        }
+
+		angular.module("umbraco").controller("My.Controller", Controller);
+
+	})();
+
+ +@param {string=} language Language of the code snippet, e.g csharp, html, css. +**/ + + +(function () { + 'use strict'; + + var umbCodeSnippet = { + templateUrl: 'views/components/umb-code-snippet.html', + controller: UmbCodeSnippetController, + controllerAs: 'vm', + transclude: true, + bindings: { + language: '<' + } + }; + + function UmbCodeSnippetController($timeout) { + + const vm = this; + + vm.page = {}; + + vm.$onInit = onInit; + vm.copySuccess = copySuccess; + vm.copyError = copyError; + + function onInit() { + vm.guid = String.CreateGuid(); + + if (vm.language) + { + switch (vm.language.toLowerCase()) { + case "csharp": + case "c#": + vm.language = "C#"; + break; + case "html": + vm.language = "HTML"; + break; + case "css": + vm.language = "CSS"; + break; + case "javascript": + vm.language = "JavaScript"; + break; + } + } + + } + + // copy to clip board success + function copySuccess() { + if (vm.page.copyCodeButtonState !== "success") { + $timeout(function () { + vm.page.copyCodeButtonState = "success"; + }); + $timeout(function () { + resetClipboardButtonState(); + }, 1000); + } + } + + // copy to clip board error + function copyError() { + if (vm.page.copyCodeButtonState !== "error") { + $timeout(function () { + vm.page.copyCodeButtonState = "error"; + }); + $timeout(function () { + resetClipboardButtonState(); + }, 1000); + } + } + + function resetClipboardButtonState() { + vm.page.copyCodeButtonState = "init"; + } + } + + angular.module('umbraco.directives').component('umbCodeSnippet', umbCodeSnippet); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 1b011d2e19..1c4bf4d583 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -312,14 +312,7 @@ Use this directive to generate a thumbnail grid of media items. scope.onDetailsHover(item, $event, hover); } }; - - scope.clickEdit = function(item, $event) { - if (scope.onClickEdit) { - scope.onClickEdit({"item": item}) - $event.stopPropagation(); - } - }; - + var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue) { if (angular.isArray(newValue)) { activate(); @@ -341,8 +334,8 @@ Use this directive to generate a thumbnail grid of media items. onDetailsHover: "=", onClick: '=', onClickName: "=", - onClickEdit: "&?", - allowOnClickEdit: "@?", + allowOpenFolder: "=", + allowOpenFile: "=", filterBy: "=", itemMaxWidth: "@", itemMaxHeight: "@", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js new file mode 100644 index 0000000000..4ac56ad13b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/emailmarketing.resource.js @@ -0,0 +1,34 @@ +/** + * @ngdoc service + * @name umbraco.resources.emailMarketingResource + * @description Used to add a backoffice user to Umbraco's email marketing system, if user opts in + * + * + **/ +function emailMarketingResource($http, umbRequestHelper) { + + // LOCAL + // http://localhost:7071/api/EmailProxy + + // LIVE + // https://emailcollector.umbraco.io/api/EmailProxy + + const emailApiUrl = 'https://emailcollector.umbraco.io/api/EmailProxy'; + + //the factory object returned + return { + + postAddUserToEmailMarketing: (user) => { + return umbRequestHelper.resourcePromise( + $http.post(emailApiUrl, + { + name: user.name, + email: user.email, + usergroup: user.userGroups // [ "admin", "sensitiveData" ] + }), + 'Failed to add user to email marketing list'); + } + }; +} + +angular.module('umbraco.resources').factory('emailMarketingResource', emailMarketingResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js index bcdaddd22f..84c0f60e59 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js @@ -164,69 +164,6 @@ function logResource($q, $http, umbRequestHelper) { 'Failed to retrieve log data for id'); }, - /** - * @ngdoc method - * @name umbraco.resources.logResource#getEntityLog - * @methodOf umbraco.resources.logResource - * - * @description - * [OBSOLETE] use getPagedEntityLog instead
- * Gets the log history for a give entity id - * - * ##usage - *
-         * logResource.getEntityLog(1234)
-         *    .then(function(log) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of entity to return log history - * @returns {Promise} resourcePromise object containing the log. - * - */ - getEntityLog: function(id) { - return umbRequestHelper.resourcePromise( - $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 - * [OBSOLETE] use getPagedUserLog instead
- * Gets the current user's log history for a given type of log entry - * - * ##usage - *
-         * logResource.getUserLog("save", new Date())
-         *    .then(function(log) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {String} type logtype to query for - * @param {DateTime} since query the log back to this date, by defalt 7 days ago - * @returns {Promise} resourcePromise object containing the log. - * - */ - getUserLog: function(type, since) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetCurrentUserLog", - [{ logtype: type }, { sinceDate: dateToValidIsoString(since) }])), - 'Failed to retrieve log data for current user of type ' + type + ' since ' + since); - }, - /** * @ngdoc method * @name umbraco.resources.logResource#getLog diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 8b922d7ec8..1d80d3a3ed 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -367,7 +367,7 @@ When building a custom infinite editor view you can use the same components as a */ function contentPicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; editor.section = "content"; editor.treeAlias = "content"; open(editor); @@ -390,7 +390,7 @@ When building a custom infinite editor view you can use the same components as a */ function contentTypePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; editor.section = "settings"; editor.treeAlias = "documentTypes"; open(editor); @@ -413,7 +413,7 @@ When building a custom infinite editor view you can use the same components as a */ function mediaTypePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; editor.section = "settings"; editor.treeAlias = "mediaTypes"; open(editor); @@ -436,7 +436,7 @@ When building a custom infinite editor view you can use the same components as a */ function memberTypePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; editor.section = "settings"; editor.treeAlias = "memberTypes"; open(editor); @@ -457,7 +457,7 @@ When building a custom infinite editor view you can use the same components as a function copy(editor) { editor.view = "views/common/infiniteeditors/copy/copy.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -477,7 +477,7 @@ When building a custom infinite editor view you can use the same components as a function move(editor) { editor.view = "views/common/infiniteeditors/move/move.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -495,7 +495,7 @@ When building a custom infinite editor view you can use the same components as a function embed(editor) { editor.view = "views/common/infiniteeditors/embed/embed.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -514,7 +514,7 @@ When building a custom infinite editor view you can use the same components as a function rollback(editor) { editor.view = "views/common/infiniteeditors/rollback/rollback.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -534,7 +534,7 @@ When building a custom infinite editor view you can use the same components as a */ function linkPicker(editor) { editor.view = "views/common/infiniteeditors/linkpicker/linkpicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -577,7 +577,7 @@ When building a custom infinite editor view you can use the same components as a */ function mediaPicker(editor) { editor.view = "views/common/infiniteeditors/mediapicker/mediapicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; editor.updatedMediaNodes = []; open(editor); } @@ -598,7 +598,7 @@ When building a custom infinite editor view you can use the same components as a */ function iconPicker(editor) { editor.view = "views/common/infiniteeditors/iconpicker/iconpicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -692,7 +692,7 @@ When building a custom infinite editor view you can use the same components as a */ function treePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -710,7 +710,7 @@ When building a custom infinite editor view you can use the same components as a */ function nodePermissions(editor) { editor.view = "views/common/infiniteeditors/nodepermissions/nodepermissions.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -728,7 +728,7 @@ When building a custom infinite editor view you can use the same components as a */ function insertCodeSnippet(editor) { editor.view = "views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -746,7 +746,7 @@ When building a custom infinite editor view you can use the same components as a */ function userGroupPicker(editor) { editor.view = "views/common/infiniteeditors/usergrouppicker/usergrouppicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -782,7 +782,7 @@ When building a custom infinite editor view you can use the same components as a */ function sectionPicker(editor) { editor.view = "views/common/infiniteeditors/sectionpicker/sectionpicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -800,7 +800,7 @@ When building a custom infinite editor view you can use the same components as a */ function insertField(editor) { editor.view = "views/common/infiniteeditors/insertfield/insertfield.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -818,7 +818,7 @@ When building a custom infinite editor view you can use the same components as a */ function templateSections(editor) { editor.view = "views/common/infiniteeditors/templatesections/templatesections.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -836,7 +836,7 @@ When building a custom infinite editor view you can use the same components as a */ function userPicker(editor) { editor.view = "views/common/infiniteeditors/userpicker/userpicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -858,7 +858,7 @@ When building a custom infinite editor view you can use the same components as a */ function itemPicker(editor) { editor.view = "views/common/infiniteeditors/itempicker/itempicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -876,7 +876,7 @@ When building a custom infinite editor view you can use the same components as a */ function macroPicker(editor) { editor.view = "views/common/infiniteeditors/macropicker/macropicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -896,7 +896,7 @@ When building a custom infinite editor view you can use the same components as a */ function memberGroupPicker(editor) { editor.view = "views/common/infiniteeditors/membergrouppicker/membergrouppicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; open(editor); } @@ -917,7 +917,7 @@ When building a custom infinite editor view you can use the same components as a */ function memberPicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; - editor.size = "small"; + if (!editor.size) editor.size = "small"; editor.section = "member"; editor.treeAlias = "member"; open(editor); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js index c123ac6cea..e5701b9de0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js @@ -148,7 +148,7 @@ angular.module('umbraco.services') break; case 1: //info - this.success(args.header, args.message); + this.info(args.header, args.message); break; case 2: //error @@ -297,4 +297,4 @@ angular.module('umbraco.services') }; return service; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index b0941bd5ad..61e3ae90ec 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -488,7 +488,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s * @methodOf umbraco.services.tinyMceService * * @description - * Creates the umbrco insert embedded media tinymce plugin + * Creates the umbraco insert embedded media tinymce plugin * * @param {Object} editor the TinyMCE editor instance */ @@ -575,7 +575,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s * @methodOf umbraco.services.tinyMceService * * @description - * Creates the umbrco insert media tinymce plugin + * Creates the umbraco insert media tinymce plugin * * @param {Object} editor the TinyMCE editor instance */ @@ -705,7 +705,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s * @methodOf umbraco.services.tinyMceService * * @description - * Creates the insert umbrco macro tinymce plugin + * Creates the insert umbraco macro tinymce plugin * * @param {Object} editor the TinyMCE editor instance */ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js index e102da5d34..62af17146c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js @@ -147,7 +147,10 @@ group.groupOrder = item.groupOrder } groupExists = true; - group.tours.push(item) + + if(item.hidden === false){ + group.tours.push(item); + } } }); @@ -157,8 +160,11 @@ if(item.groupOrder) { newGroup.groupOrder = item.groupOrder } - newGroup.tours.push(item); - groupedTours.push(newGroup); + + if(item.hidden === false){ + newGroup.tours.push(item); + groupedTours.push(newGroup); + } } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 7723c8f4bb..afd7b606e7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,5 +1,5 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, $timeout, angularHelper) { + .factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, emailMarketingResource, $timeout, angularHelper) { var currentUser = null; var lastUserId = null; @@ -262,6 +262,11 @@ angular.module('umbraco.services') /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ setUserTimeout: function (newTimeout) { setUserTimeoutInternal(newTimeout); + }, + + /** Calls out to a Remote Azure Function to deal with email marketing service */ + addUserToEmailMarketing: (user) => { + return emailMarketingResource.postAddUserToEmailMarketing(user); } }; diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 7d199c5c4f..d5c5166d21 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,6 +1,6 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', - function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) { +app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', 'localStorageService', + function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService, localStorageService) { //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so @@ -23,11 +23,35 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', appReady(data); tourService.registerAllTours().then(function () { - // Auto start intro tour + + // Start intro tour tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { // start intro tour if it hasn't been completed or disabled if (introTour && introTour.disabled !== true && introTour.completed !== true) { tourService.startTour(introTour); + localStorageService.set("introTourShown", true); + } + else { + + const introTourShown = localStorageService.get("introTourShown"); + if(!introTourShown){ + // Go & show email marketing tour (ONLY when intro tour is completed or been dismissed) + tourService.getTourByAlias("umbEmailMarketing").then(function (emailMarketingTour) { + // Only show the email marketing tour one time - dismissing it or saying no will make sure it never appears again + // Unless invoked from tourService JS Client code explicitly. + // Accepted mails = Completed and Declicned mails = Disabled + if (emailMarketingTour && emailMarketingTour.disabled !== true && emailMarketingTour.completed !== true) { + + // Only show the email tour once per logged in session + // The localstorage key is removed on logout or user session timeout + const emailMarketingTourShown = localStorageService.get("emailMarketingTourShown"); + if(!emailMarketingTourShown){ + tourService.startTour(emailMarketingTour); + localStorageService.set("emailMarketingTourShown", true); + } + } + }); + } } }); }); diff --git a/src/Umbraco.Web.UI.Client/src/less/application/umb-outline.less b/src/Umbraco.Web.UI.Client/src/less/application/umb-outline.less index 1a04dd10c8..939366d5ac 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/umb-outline.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/umb-outline.less @@ -1,3 +1,7 @@ +*:focus { + outline-color: @ui-outline; +} + .umb-outline { &:focus { outline:none; @@ -10,7 +14,28 @@ left: 0; right: 0; border-radius: 3px; - box-shadow: 0 0 2px @blueMid, inset 0 0 2px 1px @blueMid; + box-shadow: 0 0 2px 0px @ui-outline, inset 0 0 2px 2px @ui-outline; } } + + &.umb-outline--surrounding { + &:focus { + .tabbing-active &::after { + top: -6px; + bottom: -6px; + left: -6px; + right: -6px; + border-radius: 9px; + } + } + } + + &.umb-outline--thin { + &:focus { + .tabbing-active &::after { + box-shadow: 0 0 2px @ui-outline, inset 0 0 2px 1px @ui-outline; + } + } + } + } diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index f6490fc79b..0921f46aac 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -132,6 +132,7 @@ @import "components/umb-content-grid.less"; @import "components/umb-contextmenu.less"; @import "components/umb-layout-selector.less"; +@import "components/umb-mini-search.less"; @import "components/tooltip/umb-tooltip.less"; @import "components/tooltip/umb-tooltip-list.less"; @import "components/overlays/umb-overlay-backdrop.less"; @@ -140,6 +141,7 @@ @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; @import "components/umb-property-actions.less"; +@import "components/umb-code-snippet.less"; @import "components/umb-color-swatches.less"; @import "components/check-circle.less"; @import "components/umb-file-icon.less"; @@ -192,6 +194,8 @@ @import "components/contextdialogs/umb-dialog-datatype-delete.less"; +@import "components/umbemailmarketing.less"; + // Utilities @import "utilities/layout/_display.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index 85532f4231..2b50b60ae8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -23,8 +23,7 @@ border-radius: 3px; // Hover/focus state - &:hover, - &:focus { + &:hover { background: @btnBackgroundHighlight; color: @gray-4; background-position: 0 -15px; @@ -35,11 +34,6 @@ .transition(background-position .1s linear); } - // Focus state for keyboard and accessibility - &:focus { - .tab-focus(); - } - // Active state &.active, &:active { @@ -54,7 +48,7 @@ &:disabled:hover { cursor: default; border-color: @btnBorder; - .opacity(65); + .opacity(80); .box-shadow(none); } @@ -219,7 +213,7 @@ input[type="button"] { } // Made for Umbraco, 2019, used for buttons that has to stand back. .btn-white { - .buttonBackground(@btnWhiteBackground, @btnWhiteBackgroundHighlight, @btnWhiteType, @btnWhiteTypeHover); + .buttonBackground(@btnWhiteBackground, @btnWhiteBackgroundHighlight, @btnWhiteType, @btnWhiteTypeHover, @gray-10, @gray-7); } // Inverse appears as dark gray .btn-inverse { @@ -230,8 +224,7 @@ input[type="button"] { .buttonBackground(@btnNeutralBackground, @btnNeutralBackgroundHighlight); color: @gray-5; // Hover/focus state - &:hover, - &:focus { + &:hover { color: @gray-5; } @@ -261,18 +254,18 @@ input[type="button"] { .btn-outline { border: 1px solid; border-color: @gray-7; - background: @white; + background: transparent; color: @blueExtraDark; padding: 5px 13px; - transition: all .2s linear; + transition: border-color .12s linear, color .12s linear; + font-weight: 600; } -.btn-outline:hover, -.btn-outline:focus, -.btn-outline:active { +.btn-outline:hover { border-color: @ui-light-type-hover; color: @ui-light-type-hover; - background: @white; + background: transparent; + transition: border-color .12s linear, color .12s linear; } // Cross-browser Jank @@ -309,14 +302,12 @@ input[type="submit"].btn { color: @linkColor; .border-radius(0); } -.btn-link:hover, -.btn-link:focus { +.btn-link:hover { color: @linkColorHover; text-decoration: underline; background-color: transparent; } -.btn-link[disabled]:hover, -.btn-link[disabled]:focus { +.btn-link[disabled]:hover { color: @gray-4; text-decoration: none; } @@ -324,8 +315,7 @@ input[type="submit"].btn { // Make a reverse type of a button link .btn-link-reverse{ text-decoration:underline; - &:hover, - &:focus{ + &:hover { text-decoration:none; } } @@ -362,7 +352,7 @@ input[type="submit"].btn { outline: 0; -webkit-appearance: none; - &:hover, &:focus { + &:hover { color: @ui-icon-hover; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less index 064ad67438..5c77a15ec7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less @@ -86,6 +86,7 @@ } .umb-help-badge__title { + display: block; font-size: 15px; font-weight: bold; color: @black; @@ -160,6 +161,9 @@ border-radius: 0; border-bottom: 1px solid @gray-9; padding: 10px; + background: transparent; + width:100%; + border: 0 none; } .umb-help-list-item:last-child { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less index 7d91783e32..4e3741905f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less @@ -19,13 +19,13 @@ box-sizing: border-box; color: @ui-option-type; width: 100%; + outline-offset: -3px; } .umb-language-picker__expand { font-size: 14px; } -.umb-language-picker__toggle:focus, .umb-language-picker__toggle:hover { background: @ui-option-hover; color:@ui-option-type-hover; @@ -54,10 +54,10 @@ font-size: 14px; width: 100%; text-align: left; + outline-offset: -3px; } -.umb-language-picker__dropdown-item:hover, -.umb-language-picker__dropdown-item:focus { +.umb-language-picker__dropdown-item:hover { background: @ui-option-hover; text-decoration: none; color:@ui-option-type-hover; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less index 33a723a3f7..bf2f030cea 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less @@ -112,3 +112,24 @@ .umb-tour-is-visible .umb-backdrop { z-index: @zindexTourBackdrop; } + +.umb-tour__popover .underline{ + font-size: 13px; + background: transparent; + border: none; + padding: 0; +} + +.umb-tour__popover--promotion { + width: 800px; + min-height: 400px; + padding: 40px; + border-radius: @baseBorderRadius * 2; + .umb-tour-step__close { + top: 40px; + right: 40px; + } + a { + text-decoration: underline; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index 7fc965a8fa..4127c2201c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -8,21 +8,6 @@ position: relative; } -.umb-button__button:focus { - outline: none; - .tabbing-active &:after { - content: ''; - position: absolute; - z-index: 10000; - top: 0; - bottom: 0; - left: 0; - right: 0; - border-radius: 3px; - box-shadow: 0 0 2px @blueMid, inset 0 0 2px 1px @blueMid; - } -} - .umb-button__content { opacity: 1; transition: opacity 0.25s ease; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 85fcc249f9..bc84b0d35e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -164,6 +164,7 @@ a.umb-editor-header__close-split-view:hover { /* variant switcher */ .umb-variant-switcher__toggle { + position: relative; display: flex; align-items: center; padding: 0 10px; @@ -173,6 +174,8 @@ a.umb-editor-header__close-split-view:hover { text-decoration: none !important; font-size: 13px; color: @ui-action-discreet-type; + background: transparent; + border: none; max-width: 50%; white-space: nowrap; @@ -185,7 +188,7 @@ a.umb-editor-header__close-split-view:hover { } } -a.umb-variant-switcher__toggle { +button.umb-variant-switcher__toggle { transition: color 0.2s ease-in-out; &:hover { //background-color: @gray-10; @@ -242,8 +245,7 @@ a.umb-variant-switcher__toggle { border-left: 4px solid @ui-active; } -.umb-variant-switcher__item:hover, -.umb-variant-switcher__item:focus { +.umb-variant-switcher__item:hover { outline: none; } @@ -267,7 +269,7 @@ a.umb-variant-switcher__toggle { align-items: center; justify-content: center; margin-left: 5px; - top: -6px; + top: -3px; width: 14px; height: 14px; border-radius: 7px; @@ -285,8 +287,10 @@ a.umb-variant-switcher__toggle { flex: 1; cursor: pointer; padding-top: 6px !important; - padding-bottom: 6px !important; - border-left: 2px solid transparent; + padding-bottom: 6px !important; + background-color: transparent; + border: none; + border-left: 2px solid transparent; } .umb-variant-switcher__name { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 1217441f4e..4ebfa94b6f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -24,8 +24,9 @@ .umb-editor-sub-header.--state-selection { padding-left: 10px; padding-right: 10px; - background-color: @pinkLight; - border-color: @pinkLight; + background-color: @ui-selected-border; + border-color: @ui-selected-border; + color: @white; border-radius: 3px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less index df01477880..4a483ce3f0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less @@ -4,16 +4,16 @@ width: auto; margin-top:1px; - .umb-tree-item__label { - user-select: none; - } - &:hover .umb-tree-item__arrow { visibility: visible; cursor: pointer } } +.umb-tree-item__label { + user-select: none; +} + .umb-tree-item__arrow { position: relative; margin-left: -16px; @@ -92,18 +92,6 @@ color: @blue; } - .umb-options { - - &:hover i { - opacity: .7; - } - - i { - background: @ui-active-type; - transition: opacity 120ms ease; - } - } - a, .umb-tree-icon, .umb-tree-item__arrow { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 0a0fb29eed..d06c15cd30 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -99,6 +99,7 @@ body.touch .umb-tree { .umb-tree-item__inner { border: 2px solid transparent; + overflow: visible; } .umb-tree-header { @@ -176,9 +177,25 @@ body.touch .umb-tree { cursor: pointer; border-radius: @baseBorderRadius; - &:hover { - background: @btnBackgroundHighlight; + i { + height: 5px !important; + width: 5px !important; + border-radius: 20px; + display: inline-block; + margin: 0 2px 0 0; + background: @ui-active-type; + + &:last-child { + margin: 0; + } } + &:hover { + background: rgba(255, 255, 255, .5); + i { + background: @ui-active-type-hover; + } + } + // NOTE - We're having to repeat ourselves here due to an .sr-only class appearing in umbraco/lib/font-awesome/css/font-awesome.min.css &.sr-only--hoverable:hover, &.sr-only--focusable:focus { @@ -193,19 +210,6 @@ body.touch .umb-tree { border-radius: 3px; } - i { - height: 5px !important; - width: 5px !important; - border-radius: 20px; - background: @black; - display: inline-block; - margin: 0 2px 0 0; - - &:last-child { - margin: 0; - } - } - .hide-options & { display: none !important; } @@ -289,9 +293,8 @@ body.touch .umb-tree { } .no-access { - .umb-tree-icon, - .root-link, - .umb-tree-item__label { + > .umb-tree-item__inner .umb-tree-icon, + > .umb-tree-item__inner .umb-tree-item__label { color: @gray-7; cursor: not-allowed; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 0afcfdd1f9..de678f9798 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -4,6 +4,7 @@ margin-left: 0; display: flex; flex-wrap: wrap; + user-select: none; } .umb-breadcrumbs__ancestor { @@ -12,10 +13,23 @@ } .umb-breadcrumbs__action { + position: relative; background: transparent; border: 0 none; - padding: 0; - margin-top: -4px; + border-radius: 3px; + padding: 0 4px; + color: @ui-option-type; + + &.--current { + font-weight: bold; + pointer-events: none; + } + + &:hover { + color: @ui-option-type-hover; + background-color: @white; + } + } .umb-breadcrumbs__ancestor-link, @@ -26,6 +40,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + padding: 0 4px; } .umb-breadcrumbs__ancestor-link { @@ -39,13 +54,13 @@ .umb-breadcrumbs__separator { position: relative; top: 1px; - margin-left: 5px; - margin-right: 5px; + margin: 0 1px; + margin-top: -3px; color: @gray-7; } input.umb-breadcrumbs__add-ancestor { - height: 25px; - margin: 0 0 0 3px; + height: 24px; + margin: -2px 0 -2px 3px; width: 100px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkmark.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkmark.less index 021fc8cc9b..f82e47bf88 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkmark.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkmark.less @@ -2,21 +2,30 @@ border: 2px solid @white; width: 25px; height: 25px; - border: 1px solid @gray-7; + border: 1px solid @ui-action-discreet-border; border-radius: 3px; box-sizing: border-box; display: flex; justify-content: center; align-items: center; - color: @gray-7; + color: @ui-selected-type; cursor: pointer; font-size: 15px; + &:hover { + border-color:@ui-action-discreet-border-hover; + color: @ui-selected-type-hover; + } } .umb-checkmark--checked { - background: @ui-active; - border-color: @ui-active; + background: @ui-selected-border; + border-color: @ui-selected-border; color: @white; + &:hover { + background: @ui-selected-border-hover; + border-color: @ui-selected-border-hover; + color: @white; + } } .umb-checkmark--xs { @@ -45,4 +54,4 @@ width: 50px; height: 50px; font-size: 20px; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-code-snippet.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-code-snippet.less new file mode 100644 index 0000000000..b372841910 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-code-snippet.less @@ -0,0 +1,43 @@ +.umb-code-snippet { + + .umb-code-snippet__header { + box-sizing: content-box; + background-color: @gray-10; + display: flex; + flex-direction: row; + font-size: .8rem; + border: 1px solid @gray-8; + border-radius: 3px 3px 0 0; + border-bottom: 0; + margin-top: 16px; + min-height: 30px; + + .language { + display: flex; + align-items: center; + justify-content: flex-start; + flex-grow: 1; + padding: 2px 10px; + } + + button { + background-color: transparent; + border: none; + border-left: 1px solid @gray-8; + border-radius: 0; + color: #000; + + &:hover { + background-color: @grayLighter; + } + } + } + + .umb-code-snippet__content { + pre { + border-radius: 0 0 3px 3px; + overflow: auto; + white-space: nowrap; + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index f27e1e4ec8..622dcb8b0a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -29,15 +29,15 @@ border-radius: 5px; box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); pointer-events: none; + transition: opacity 100ms; } - } } .umb-content-grid__item:hover { &::before { - opacity: .33; + opacity: .2; } } .umb-content-grid__item.-selected:hover { @@ -46,6 +46,7 @@ } } + .umb-content-grid__icon-container { height: 75px; display: flex; @@ -66,8 +67,10 @@ } .umb-content-grid__item-name { + position: relative; + padding: 5px; + margin: -5px -5px 15px -5px; font-weight: bold; - margin-bottom: 15px; line-height: 1.4em; display: inline-flex; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less index c26c89a478..6a6a8f9f5b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less @@ -4,6 +4,7 @@ &__action, > a { + position: relative; background: transparent; text-align: center; cursor: pointer; @@ -18,26 +19,20 @@ align-items: center; justify-content: center; height: calc(~'@{editorHeaderHeight}'- ~'1px'); // need to offset the 1px border-bottom on .umb-editor-header - avoids overflowing top of the container - position: relative; color: @ui-active-type; - &:focus, &:hover { color: @ui-active-type-hover !important; text-decoration: none; } - &:focus { - outline: none; - } - - &::after { + &::before { content: ""; + position: absolute; height: 0px; left: 8px; right: 8px; background-color: @ui-light-active-border; - position: absolute; bottom: 0; border-radius: 3px 3px 0 0; opacity: 0; @@ -47,14 +42,13 @@ &.is-active { color: @ui-light-active-type; - &::after { + &::before { opacity: 1; height: 4px; } } } - &__action:focus, &__action:active, & > a:active { .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); @@ -111,7 +105,6 @@ &__anchor_dropdown { // inherits from .dropdown-menu margin: 0; - overflow: hidden; // center align horizontal left: 50%; @@ -122,7 +115,7 @@ li { &.is-active a { - border-left-color: @ui-selected-border; + border-left-color: @ui-active; } a { @@ -192,4 +185,4 @@ &::after { background-color: @red; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less index e25349f555..1ae476d584 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less @@ -41,12 +41,17 @@ margin-top: 10px; } +button.umb-grid-selector__item { + width: 169px; + height: 194px; +} + .umb-grid-selector__item-icon { - font-size: 50px; - color: @gray-8; - display: block; - line-height: 50px; - margin-bottom: 15px; + font-size: 50px; + color: @gray-8; + display: block; + line-height: 50px; + margin-bottom: 15px; } .umb-grid-selector__item-label { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less index 9ebd6d6e5d..c0ac89622d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less @@ -6,7 +6,8 @@ .umb-layout-selector__active-layout { background: transparent; box-sizing: border-box; - border: 1px solid @inputBorder; + border: 1px solid @ui-action-discreet-border; + color: @ui-action-discreet-type; cursor: pointer; height: 30px; width: 30px; @@ -17,7 +18,8 @@ } .umb-layout-selector__active-layout:hover { - border-color: @inputBorderFocus; + border-color: @ui-action-discreet-border-hover; + color: @ui-action-discreet-type-hover; } .umb-layout-selector__dropdown { @@ -31,6 +33,7 @@ flex-direction: column; transform: translate(-50%,0); left: 50%; + border-radius: 3px; } .umb-layout-selector__dropdown-item { @@ -46,11 +49,11 @@ } .umb-layout-selector__dropdown-item:hover { - border: 1px solid @gray-8; + border: 1px solid @ui-action-discreet-border; } .umb-layout-selector__dropdown-item.-active { - border: 1px solid @blue; + border: 1px solid @ui-action-discreet-border-hover; } .umb-layout-selector__dropdown-item-icon, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 4feadc272c..5d6b7ad962 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -40,25 +40,41 @@ } } -.umb-media-grid__item.-selectable { +.umb-media-grid__item.-selectable, +.umb-media-grid__item.-folder {// If folders isnt selectable, they opens if clicked, therefor... cursor: pointer; - - .tabbing-active &:focus { - outline: 2px solid @inputBorderTabFocus; - } } .umb-media-grid__item.-file { background-color: @white; } +.umb-media-grid__item.-folder { + + &.-selectable { + .media-grid-item-edit:hover .umb-media-grid__item-name, + .media-grid-item-edit:focus .umb-media-grid__item-name { + text-decoration: underline; + } + } + + &.-unselectable { + &:hover, &:focus { + .umb-media-grid__item-name { + text-decoration: underline; + } + } + } +} + + .umb-media-grid__item.-selected { color:@ui-selected-type; .umb-media-grid__item-overlay { color: @ui-selected-type; } } -.umb-media-grid__item.-selected, +.umb-media-grid__item.-selected, .umb-media-grid__item.-selectable:hover { &::before { content: ""; @@ -139,10 +155,10 @@ background: fade(@white, 92%); transition: opacity 150ms; - &:hover { + &.-can-open:hover { text-decoration: underline; } - + .tabbing-active &:focus { opacity: 1; } @@ -190,7 +206,7 @@ align-items: center; color: @black; transition: opacity 150ms; - + &:hover { color: @ui-action-discreet-type-hover; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less new file mode 100644 index 0000000000..ac15b3dcf8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less @@ -0,0 +1,44 @@ +.umb-mini-search { + position: relative; + display: block; + + .icon { + position: absolute; + padding: 5px 8px; + pointer-events: none; + top: 2px; + color: @ui-action-discreet-type; + transition: color .1s linear; + } + + input { + width: 0px; + padding-left:24px; + margin-bottom: 0px; + background-color: transparent; + border-color: @ui-action-discreet-border; + transition: background-color .1s linear, border-color .1s linear, color .1s linear, width .1s ease-in-out, padding-left .1s ease-in-out; + } + + &:focus-within, &:hover { + .icon { + color: @ui-action-discreet-type-hover; + } + input { + color: @ui-action-discreet-border-hover; + border-color: @ui-action-discreet-border-hover; + } + } + + input:focus, &:focus-within input { + background-color: white; + color: @ui-action-discreet-border-hover; + border-color: @ui-action-discreet-border-hover; + } + + input:focus, &:focus-within input, &.--has-value input { + width: 190px; + padding-left:30px; + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 699496f5d3..4168ab3c39 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -41,11 +41,8 @@ } .umb-nested-content__item.ui-sortable-placeholder { - background: @gray-10; - border: 1px solid @gray-9; + margin-top: 1px; visibility: visible !important; - height: 55px; - margin-top: -1px; } .umb-nested-content__item--single > .umb-nested-content__content { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less index 03816637a7..d8fb3b4d8f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less @@ -5,6 +5,7 @@ .umb-progress-circle__view-box { position: absolute; transform: rotate(-90deg); + right: 0; } // circle highlight on progressbar diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-sub-views.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-sub-views.less index d3ce368356..cc6be8fa37 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-sub-views.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-sub-views.less @@ -8,7 +8,6 @@ padding-left: 0; padding-right: 0; &:focus { - outline: none; text-decoration: none; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 94c0318fca..202c488bb4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -34,8 +34,12 @@ text-decoration: none; padding: 0; margin-left: 1px; + body:not(.tabbing-active) & { + outline: 0; + } } + input.umb-table__input { margin: 0 auto; } @@ -47,6 +51,8 @@ input.umb-table__input { .umb-table-head { font-size: 14px; font-weight: bold; + + color: @ui-disabled-type; } .umb-table-head__link { @@ -68,10 +74,12 @@ input.umb-table__input { .umb-table-head__link.sortable { cursor: pointer; + color: @ui-action-discreet-type; &:hover { - text-decoration: none; - color: @black; + color: @ui-action-discreet-type-hover; } + + outline-offset: 1px; } .umb-table-thead__icon { @@ -129,6 +137,9 @@ input.umb-table__input { &::before { opacity:.66; } + .umb-table-body__checkicon { + color: @ui-selected-border; + } } } @@ -141,21 +152,19 @@ input.umb-table__input { } .umb-table-body__link { + position: relative; color: @ui-option-type; font-size: 14px; font-weight: bold; text-decoration: none; - &:hover, &:focus { + &:hover { color: @ui-option-type-hover; text-decoration: underline; - outline: none; } } -.umb-table-body__icon, -.umb-table-body__icon[class^="icon-"], -.umb-table-body__icon[class*=" icon-"] { +.umb-table-body__icon { margin: 0 auto; font-size: 20px; line-height: 20px; @@ -164,13 +173,11 @@ input.umb-table__input { text-decoration: none; } -.umb-table-body__checkicon, -.umb-table-body__checkicon[class^="icon-"], -.umb-table-body__checkicon[class*=" icon-"] { +.umb-table-body__checkicon { display: none; font-size: 18px; line-height: 20px; - color: @green; + color: @ui-selected-border; } .umb-table-body .umb-table__name { @@ -179,7 +186,8 @@ input.umb-table__input { font-weight: bold; a { color: @ui-option-type; - &:hover, &:focus { + outline-offset: 1px; + &:hover { color: @ui-option-type-hover; text-decoration: underline; } @@ -249,8 +257,8 @@ input.umb-table__input { flex-flow: row nowrap; flex: 1 1 5%; position: relative; - margin: auto 14px; - padding: 6px 2px; + margin: auto 0; + padding: 6px 16px; text-align: left; overflow:hidden; } @@ -268,8 +276,8 @@ input.umb-table__input { .umb-table-cell:first-of-type:not(.not-fixed) { flex: 0 0 25px; - margin: 0 0 0 15px; - padding: 15px 0; + margin: 0; + padding: 15px 0 15px 15px; } .umb-table-cell--auto-width { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less b/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less new file mode 100644 index 0000000000..f4b3183045 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umbemailmarketing.less @@ -0,0 +1,44 @@ +.umb-email-marketing { + + h2 { + font-weight: 800; + max-width: 26ex; + margin-top: 20px; + } + + .layout { + display: flex; + align-items: center; + align-content: stretch; + + .primary { + flex-basis: 50%; + padding-right: 40px; + padding-top: 20px; + padding-bottom: 20px; + .notice { + color: @gray-5; + font-style: italic; + a { + color: @gray-5; + &:hover { + color: @ui-action-type-hover; + } + } + } + } + + .secondary { + flex-basis: 50%; + svg { + height: 200px; + width: 100%; + margin-top: -60px; + } + } + } + + .cta { + text-align: right; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-group-picker-list.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-group-picker-list.less index dff78ce627..0a06120b11 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-group-picker-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-group-picker-list.less @@ -7,11 +7,17 @@ display: flex; margin-bottom: 5px; padding: 10px; + position: relative; } -.umb-user-group-picker-list-item:active, -.umb-user-group-picker-list-item:focus { - text-decoration: none; +.umb-user-group-picker__action{ + background: transparent; + border: 0 none; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } .umb-user-group-picker-list-item:hover { @@ -35,4 +41,4 @@ .umb-user-group-picker-list-item__permission { font-size: 13px; color: @gray-4; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 72abb3ba00..0600c9aab6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -252,7 +252,7 @@ input[type="color"], outline: 0; .tabbing-active & { - outline: 2px solid @inputBorderTabFocus; + outline: 2px solid @ui-outline; } } } @@ -297,11 +297,11 @@ select[size] { } // Focus for select, file, radio, and checkbox -select:focus, -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - .tab-focus(); +select, +input[type="file"], +input[type="radio"], +input[type="checkbox"] { + .umb-outline(); } diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index 3ead4d6905..8d3117febe 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -185,40 +185,38 @@ iframe, .content-column-body { // Inline code // 1: Revert border radius to match look and feel of 7.4+ -code{ - .border-radius(@baseBorderRadius); // 1 +code { + .border-radius(@baseBorderRadius); // 1 } // Blocks of code // 1: Wrapping code is unreadable on small devices. pre { - display: block; - padding: (@baseLineHeight - 1) / 2; - margin: 0 0 @baseLineHeight / 2; - font-family: @sansFontFamily; - //font-size: @baseFontSize - 1; // 14px to 13px - color: @gray-2; - line-height: @baseLineHeight; - white-space: pre-wrap; // 1 - overflow-x: auto; // 1 - background-color: @gray-10; - border: 1px solid @gray-8; - .border-radius(@baseBorderRadius); + display: block; + padding: (@baseLineHeight - 1) / 2; + margin: 0 0 @baseLineHeight / 2; + font-family: @sansFontFamily; + color: @gray-2; + line-height: @baseLineHeight; + white-space: pre-wrap; // 1 + overflow-x: auto; // 1 + background-color: @brownGrayLight; + border: 1px solid @gray-8; + .border-radius(@baseBorderRadius); + // Make prettyprint styles more spaced out for readability + &.prettyprint { + margin-bottom: @baseLineHeight; + } - // Make prettyprint styles more spaced out for readability - &.prettyprint { - margin-bottom: @baseLineHeight; - } - - // Account for some code outputs that place code tags in pre tags - code { - padding: 0; - white-space: pre; // 1 - word-wrap: normal; // 1 - background-color: transparent; - border: 0; - } + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + white-space: pre; // 1 + word-wrap: normal; // 1 + background-color: transparent; + border: 0; + } } /* Styling for content/media sort order dialog */ diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index e964ed3c6f..4e24161e59 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -3,6 +3,7 @@ @import "variables.less"; // Modify this for custom colors, font-sizes, etc @import "colors.less"; @import "mixins.less"; +@import "application/umb-outline.less"; @import "buttons.less"; @import "forms.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 975dbdbd4a..fe8af6dbc4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -1,6 +1,10 @@ // Listview // ------------------------- +.umb-listview { + min-height: 100px; +} + .umb-listview table { border: 1px solid @gray-8; } @@ -43,6 +47,15 @@ /* add padding */ .left-addon input[type="text"] { padding-left: 30px !important; padding-right: 6px; } .right-addon input[type="text"] { padding-right: 30px; padding-left: 6px !important; } + + &__label-icon{ + width: 30px; + height: 30px; + position: absolute; + top: -1px; + left:0; + margin:0 + } } .umb-listview table form { @@ -136,7 +149,36 @@ /* TEMP */ .umb-minilistview { - .umb-table-row.not-allowed { opacity: 0.6; cursor: not-allowed; } + .umb-table-row.not-allowed { + opacity: 0.6; + cursor: not-allowed; + } + + div.umb-mini-list-view__breadcrumb { + margin-bottom: 10px; + } + + div.no-display { + display: none + } + + div.umb-table-cell-padding { + padding-top: 8px; + padding-bottom: 8px; + } + + div.umb-table-cell .form-search { + width: 100%; + margin-right: 0; + + input { + width: 100%; + } + + .icon-search { + font-size: 14px; + } + } } .umb-listview .table-striped tbody td { diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 0d646d11c6..b34f313435 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -117,6 +117,12 @@ h5.-black { } .umb-control-group { position: relative; + + &.umb-control-group__listview { + // position: relative messes up the listview status messages (e.g. "no search results") + position: unset; + } + &::after { content: ''; display:block; diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index 21b9c5c550..efc0178ca2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -30,7 +30,6 @@ outline: thin dotted @gray-3; // Webkit outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; } // Center-align a block level element @@ -435,7 +434,7 @@ // Button backgrounds // ------------------ -.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: @white, @textColorHover: @textColor) { +.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: @white, @textColorHover: @textColor, @disabledColor: @sand-1, @disabledTextColor: @white) { color: @textColor; border-color: @startColor @startColor darken(@startColor, 15%); @@ -449,14 +448,14 @@ } // in these cases the gradient won't cover the background, so we override - &:hover, &:focus, &:active, &.active { + &:hover { color: @textColorHover; background-color: @hoverColor; } &.disabled, &[disabled] { - color: @white; - background-color: @sand-1; + background-color: @disabledColor; + color: @disabledTextColor; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index 5b97464e31..c347404619 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -233,11 +233,14 @@ } .dropdown-menu > li > a { + position: relative; padding: 8px 20px; color: @ui-option-type; + text-decoration: none; } .dropdown-menu > li > button { + position: relative; background: transparent; border: 0; padding: 8px 20px; @@ -253,11 +256,9 @@ } .dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, .dropdown-menu > li > button:hover, -.dropdown-menu > li > button:focus, .dropdown-submenu:hover > a, -.dropdown-submenu:focus > a { +.dropdown-submenu:hover > button { color: @ui-option-type-hover; background: @ui-option-hover; } @@ -300,8 +301,7 @@ // Active:hover/:focus dropdown links // ------------------------- -.nav > .dropdown.active > a:hover, -.nav > .dropdown.active > a:focus { +.nav > .dropdown.active > a:hover { cursor: pointer; } @@ -309,24 +309,21 @@ // ------------------------- .nav-tabs .open .dropdown-toggle, .nav-pills .open .dropdown-toggle, -.nav > li.dropdown.open.active > a:hover, -.nav > li.dropdown.open.active > a:focus { +.nav > li.dropdown.open.active > a:hover { /*color: @white;*/ background-color: @gray-8; border-color: @gray-8; } .nav li.dropdown.open .caret, .nav li.dropdown.open.active .caret, -.nav li.dropdown.open a:hover .caret, -.nav li.dropdown.open a:focus .caret { +.nav li.dropdown.open a:hover .caret { border-top-color: @white; border-bottom-color: @white; .opacity(100); } // Dropdowns in stacked tabs -.tabs-stacked .open > a:hover, -.tabs-stacked .open > a:focus { +.tabs-stacked .open > a:hover { border-color: @gray-8; } @@ -377,15 +374,13 @@ } .tabs-below > .nav-tabs > li > a { .border-radius(0 0 4px 4px); - &:hover, - &:focus { + &:hover { border-bottom-color: transparent; border-top-color: @gray-8; } } .tabs-below > .nav-tabs > .active > a, -.tabs-below > .nav-tabs > .active > a:hover, -.tabs-below > .nav-tabs > .active > a:focus { +.tabs-below > .nav-tabs > .active > a:hover { border-color: transparent @gray-8 @gray-8 @gray-8; } @@ -414,13 +409,11 @@ margin-right: -1px; .border-radius(4px 0 0 4px); } -.tabs-left > .nav-tabs > li > a:hover, -.tabs-left > .nav-tabs > li > a:focus { +.tabs-left > .nav-tabs > li > a:hover { border-color: @gray-10 @gray-8 @gray-10 @gray-10; } .tabs-left > .nav-tabs .active > a, -.tabs-left > .nav-tabs .active > a:hover, -.tabs-left > .nav-tabs .active > a:focus { +.tabs-left > .nav-tabs .active > a:hover { border-color: @gray-8 transparent @gray-8 @gray-8; *border-right-color: @white; } @@ -435,13 +428,11 @@ margin-left: -1px; .border-radius(0 4px 4px 0); } -.tabs-right > .nav-tabs > li > a:hover, -.tabs-right > .nav-tabs > li > a:focus { +.tabs-right > .nav-tabs > li > a:hover { border-color: @gray-10 @gray-10 @gray-10 @gray-8; } .tabs-right > .nav-tabs .active > a, -.tabs-right > .nav-tabs .active > a:hover, -.tabs-right > .nav-tabs .active > a:focus { +.tabs-right > .nav-tabs .active > a:hover { border-color: @gray-8 @gray-8 @gray-8 transparent; *border-left-color: @white; } @@ -456,8 +447,7 @@ color: @gray-8; } // Nuke hover/focus effects -.nav > .disabled > a:hover, -.nav > .disabled > a:focus { +.nav > .disabled > a:hover { text-decoration: none; background-color: transparent; cursor: default; diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index bad0ab9715..40c70f5331 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -341,6 +341,7 @@ .umb-panel-header-icon { cursor: pointer; margin-right: 5px; + margin-top: -6px; height: 50px; display: flex; justify-content: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 5a71635c4d..112f94572d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -249,26 +249,11 @@ transition: all 150ms ease-in-out; - &:focus, + &:active, &:hover { color: @ui-action-discreet-type-hover; border-color: @ui-action-discreet-type-hover; } - - &:focus { - outline: none; - .tabbing-active &:after { - content: ''; - position: absolute; - z-index: 10000; - top: -6px; - bottom: -6px; - left: -6px; - right: -6px; - border-radius: 3px; - box-shadow: 0 0 2px @blueMid, inset 0 0 2px 1px @blueMid; - } - } } .umb-mediapicker .label { diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 6071c4a5ef..a906bc0eed 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -108,6 +108,7 @@ //@blueLight: #4f89de; @blue: #2E8AEA; @blueMid: #2152A3;// updated 2019 +@blueMidLight: rgb(99, 174, 236); @blueDark: #3544b1;// updated 2019 @blueExtraDark: #1b264f;// added 2019 @blueLight: #ADD8E6; @@ -139,6 +140,9 @@ @ui-option-disabled-type-hover: @gray-5; @ui-option-disabled-hover: @sand-7; +@ui-disabled-type: @gray-6; +@ui-disabled-border: @gray-6; + //@ui-active: #346ab3; @ui-active: @pinkLight; @ui-active-blur: @brownLight; @@ -149,8 +153,8 @@ @ui-selected-hover: ligthen(@sand-5, 10); @ui-selected-type: @blueExtraDark; @ui-selected-type-hover: @blueMid; -@ui-selected-border: @pinkLight; -@ui-selected-border-hover: darken(@pinkLight, 10); +@ui-selected-border: @blueDark; +@ui-selected-border-hover: darken(@blueDark, 10); @ui-light-border: @pinkLight; @ui-light-type: @gray-4; @@ -175,6 +179,8 @@ @ui-action-discreet-border: @gray-7; @ui-action-discreet-border-hover: @blueMid; +@ui-outline: @blueMidLight; + @type-white: @white; @type-black: @blueNight; @@ -255,7 +261,7 @@ // Buttons // ------------------------- @btnBackground: @gray-9; -@btnBackgroundHighlight: @gray-9; +@btnBackgroundHighlight: @gray-10; @btnBorder: @gray-9; @btnPrimaryBackground: @ui-btn-positive; @@ -293,7 +299,7 @@ @inputBackground: @white; @inputBorder: @gray-8; @inputBorderFocus: @gray-7; -@inputBorderTabFocus: @blueExtraDark; +@inputBorderTabFocus: @ui-outline; @inputBorderRadius: 0; @inputDisabledBackground: @gray-10; @formActionsBackground: @gray-9; @@ -448,7 +454,7 @@ @successBorder: transparent; @infoText: @white; -@infoBackground: @turquoise-d1; +@infoBackground: @blueDark; @infoBorder: transparent; @alertBorderRadius: 0; diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 93870f8a56..883907d1dc 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -67,13 +67,18 @@ function MainController($scope, $location, appState, treeService, notificationsS }; var evts = []; - + //when a user logs out or timesout evts.push(eventsService.on("app.notAuthenticated", function (evt, data) { $scope.authenticated = null; $scope.user = null; const isTimedOut = data && data.isTimedOut ? true : false; $scope.showLoginScreen(isTimedOut); + + // Remove the localstorage items for tours shown + // Means that when next logged in they can be re-shown if not already dismissed etc + localStorageService.remove("emailMarketingTourShown"); + localStorageService.remove("introTourShown"); })); evts.push(eventsService.on("app.userRefresh", function(evt) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index 4ae3121098..96f4a404bc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -17,17 +17,21 @@
- -
- +
+ -
+
@@ -85,9 +89,9 @@ @@ -96,7 +100,7 @@
- +
Visit umbraco.tv
The best Umbraco video tutorials @@ -104,7 +108,7 @@
- +
Visit our.umbraco.com
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index bb7ce6f727..01f61a16ae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Editors.MediaPickerController", - function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { + function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService) { var vm = this; @@ -22,7 +22,6 @@ angular.module("umbraco") vm.clickHandler = clickHandler; vm.clickItemName = clickItemName; - vm.editMediaItem = editMediaItem; vm.gotoFolder = gotoFolder; var dialogOptions = $scope.model; @@ -37,8 +36,7 @@ angular.module("umbraco") $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); $scope.lockedFolder = true; - $scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false; - + var userStartNodes = []; var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; @@ -132,7 +130,7 @@ angular.module("umbraco") // ID of a UDI or legacy int ID still could be null/undefinied here // As user may dragged in an image that has not been saved to media section yet - if(id){ + if (id) { entityResource.getById(id, "Media") .then(function (node) { $scope.target = node; @@ -144,8 +142,7 @@ angular.module("umbraco") openDetailsDialog(); } }, gotoStartNode); - } - else { + } else { // No ID set - then this is going to be a tmpimg that has not been uploaded // User editing this will want to be changing the ALT text openDetailsDialog(); @@ -254,11 +251,14 @@ angular.module("umbraco") } } - function clickItemName(item) { + function clickItemName(item, event, index) { if (item.isFolder) { gotoFolder(item); } - } + else { + $scope.clickHandler(item, event, index); + } + }; function selectMedia(media) { if (!media.selectable) { @@ -510,30 +510,6 @@ angular.module("umbraco") } } - function editMediaItem(item) { - var mediaEditor = { - id: item.id, - submit: function (model) { - editorService.close() - // update the media picker item in the picker so it matched the saved media item - // the media picker is using media entities so we get the - // entity so we easily can format it for use in the media grid - if (model && model.mediaNode) { - entityResource.getById(model.mediaNode.id, "media") - .then(function (mediaEntity) { - angular.extend(item, mediaEntity); - setMediaMetaData(item); - setUpdatedMediaNodes(item); - }); - } - }, - close: function (model) { - setUpdatedMediaNodes(item); - editorService.close(); - } - }; - editorService.mediaEditor(mediaEditor); - }; /** * Called when the umbImageGravity component updates the focal point value diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index 373dfbcba7..917010dbb6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -56,19 +56,19 @@
@@ -159,14 +159,11 @@ -
{{model.result.queryExpression}}
- - copy to clipboard - + {{model.result.queryExpression}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html index e2ae1ab524..b82b919f9f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html @@ -13,10 +13,10 @@ - + - + - + - + - No user groups have been added + No user groups have been added - + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js new file mode 100644 index 0000000000..8ecc737278 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.controller.js @@ -0,0 +1,24 @@ +(function () { + "use strict"; + + function EmailsController($scope, userService) { + + var vm = this; + + vm.optIn = function() { + // Get the current user in backoffice + userService.getCurrentUser().then(function(user){ + // Send this user along to opt in + // It's a fire & forget - not sure we need to check the response + userService.addUserToEmailMarketing(user); + }); + + // Mark Tour as complete + // This is also can help us indicate that the user accepted + // Where disabled is set if user closes modal or chooses NO + $scope.model.completeTour(); + } + } + + angular.module("umbraco").controller("Umbraco.Tours.UmbEmailMarketing.EmailsController", EmailsController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.html new file mode 100644 index 0000000000..887624ed05 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbEmailMarketing/emails/emails.html @@ -0,0 +1,26 @@ +
+ + + +

{{ model.currentStep.title }}

+ +
+ +
+
+
+ + +
+ paperplane +
+
+ +
+ + +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html index 9a2fe96289..e358d75b9e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html @@ -4,7 +4,7 @@
-
+
@@ -29,7 +29,7 @@ total-steps="model.steps.length">
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html index 0c4c58c38f..483261a5ad 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button.html @@ -7,7 +7,12 @@
- + {{vm.buttonLabel}} @@ -18,7 +23,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index d72e977010..c35686acd1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -169,7 +169,7 @@ ng-change="updateTemplate(node.template)"> -
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html index 7430d45ce6..8496aab80c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html @@ -39,10 +39,10 @@ autocomplete="off" maxlength="255" /> - + {{vm.currentVariant.language.name}} @@ -50,10 +50,10 @@ - +
Open in split view
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index c46efb7b74..e1bc01a7a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -12,13 +12,17 @@
-
- -
- -
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html index dda8fa70f4..d743907d07 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html @@ -4,7 +4,7 @@ hotkey="{{::vm.hotkey}}" hotkey-when-hidden="true" ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}" - class="umb-sub-views-nav-item__action"> + class="umb-sub-views-nav-item__action umb-outline umb-outline--thin"> {{ vm.item.name }}
{{vm.item.badge.count}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index c2f9ceebc4..ca57679f51 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -1,6 +1,6 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html index fb8ae6b22f..1d8829b4ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-item.html @@ -11,7 +11,7 @@ - {{node.name}} + {{node.name}} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-checkmark.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-checkmark.html index 89201a144f..faf4dca742 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-checkmark.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-checkmark.html @@ -1 +1 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-code-snippet.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-code-snippet.html new file mode 100644 index 0000000000..199d7dec56 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-code-snippet.html @@ -0,0 +1,23 @@ +
+
+ {{vm.language}} + + + + +
+
+
+            
+        
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html index 93fa590f68..0276ae2a98 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html @@ -1,14 +1,14 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html index c6c841f8b1..1fa917a07f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html @@ -1,6 +1,6 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index e14315f9f4..da1e5c3aa7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -1,18 +1,18 @@
-
- +

{{ miniListView.node.name }}

- + Back / @@ -30,13 +30,12 @@
- -
-
[PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.Macros)] + [MacrosControllerConfiguration] public class MacrosController : BackOfficeNotificationsController { private readonly IMacroService _macroService; - public MacrosController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + public MacrosController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _macroService = Services.MacroService; } + /// + /// Configures this controller with a custom action selector + /// + private class MacrosControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) + )); + } + } + /// /// Creates a new macro /// @@ -97,39 +113,43 @@ namespace Umbraco.Web.Editors return this.ReturnErrorResponse($"Macro with id {id} does not exist"); } - var macroDisplay = new MacroDisplay - { - Alias = macro.Alias, - Id = macro.Id, - Key = macro.Key, - Name = macro.Name, - CacheByPage = macro.CacheByPage, - CacheByUser = macro.CacheByMember, - CachePeriod = macro.CacheDuration, - View = macro.MacroSource, - RenderInEditor = !macro.DontRender, - UseInEditor = macro.UseInEditor, - Path = $"-1,{macro.Id}" - }; - - var parameters = new List(); - - foreach (var param in macro.Properties.Values.OrderBy(x => x.SortOrder)) - { - parameters.Add(new MacroParameterDisplay - { - Editor = param.EditorAlias, - Key = param.Alias, - Label = param.Name, - Id = param.Id - }); - } - - macroDisplay.Parameters = parameters; + var macroDisplay = MapToDisplay(macro); return this.Request.CreateResponse(HttpStatusCode.OK, macroDisplay); } + [HttpGet] + public HttpResponseMessage GetById(Guid id) + { + var macro = _macroService.GetById(id); + + if (macro == null) + { + return this.ReturnErrorResponse($"Macro with id {id} does not exist"); + } + + var macroDisplay = MapToDisplay(macro); + + return this.Request.CreateResponse(HttpStatusCode.OK, macroDisplay); + } + + [HttpGet] + public HttpResponseMessage GetById(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi == null) + this.ReturnErrorResponse($"Macro with id {id} does not exist"); + + var macro = _macroService.GetById(guidUdi.Guid); + if (macro == null) + { + return this.ReturnErrorResponse($"Macro with id {id} does not exist"); + } + + var macroDisplay = MapToDisplay(macro); + + return this.Request.CreateResponse(HttpStatusCode.OK, macroDisplay); + } [HttpPost] public HttpResponseMessage DeleteById(int id) @@ -384,5 +404,29 @@ namespace Umbraco.Web.Editors return files; } + + /// + /// Used to map an instance to a + /// + /// + /// + private MacroDisplay MapToDisplay(IMacro macro) + { + var display = Mapper.Map(macro); + + var parameters = macro.Properties.Values + .OrderBy(x => x.SortOrder) + .Select(x => new MacroParameterDisplay() + { + Editor = x.EditorAlias, + Key = x.Alias, + Label = x.Name, + Id = x.Id + }); + + display.Parameters = parameters; + + return display; + } } } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 51b6ad2a3d..aa6c486840 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -48,6 +48,7 @@ using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Strings; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Editors { @@ -72,8 +73,9 @@ namespace Umbraco.Web.Editors IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IMediaFileSystem mediaFileSystem, - IShortStringHelper shortStringHelper) - : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + IShortStringHelper shortStringHelper, + UmbracoMapper umbracoMapper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); _mediaFileSystem = mediaFileSystem; diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index b0f21e26e0..bbfef7414f 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Strings; using Umbraco.Web.Composing; using Constants = Umbraco.Core.Constants; using IMediaType = Umbraco.Core.Models.IMediaType; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Editors { @@ -39,8 +40,8 @@ namespace Umbraco.Web.Editors { private readonly IShortStringHelper _shortStringHelper; - public MediaTypeController(ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) - : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + public MediaTypeController(ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _shortStringHelper = shortStringHelper; } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 4c395a56f9..526924933a 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -32,6 +32,7 @@ using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Strings; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Editors { @@ -56,8 +57,9 @@ namespace Umbraco.Web.Editors IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, - IShortStringHelper shortStringHelper) - : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + IShortStringHelper shortStringHelper, + UmbracoMapper umbracoMapper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _passwordConfig = passwordConfig ?? throw new ArgumentNullException(nameof(passwordConfig)); _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index 8f425323d7..1270e5fc0a 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -18,6 +18,7 @@ using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Editors { @@ -28,8 +29,8 @@ namespace Umbraco.Web.Editors [UmbracoTreeAuthorize(new string[] { Constants.Trees.MemberTypes, Constants.Trees.Members})] public class MemberTypeController : ContentTypeControllerBase { - public MemberTypeController(ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) - : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + public MemberTypeController(ICultureDictionary cultureDictionary, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper) + : base(cultureDictionary, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { } diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 7dec24d9a4..5486b32655 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Cache; using Umbraco.Web.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Packaging; @@ -38,8 +39,8 @@ namespace Umbraco.Web.Editors private readonly IUmbracoVersion _umbracoVersion; public PackageInstallController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, - IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, IUmbracoVersion umbracoVersion) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, IUmbracoVersion umbracoVersion, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _umbracoVersion = umbracoVersion; diff --git a/src/Umbraco.Web/Editors/RelationTypeController.cs b/src/Umbraco.Web/Editors/RelationTypeController.cs index 55b3ced23a..73a659bde5 100644 --- a/src/Umbraco.Web/Editors/RelationTypeController.cs +++ b/src/Umbraco.Web/Editors/RelationTypeController.cs @@ -17,6 +17,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Editors { @@ -37,8 +38,9 @@ namespace Umbraco.Web.Editors IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, - IShortStringHelper shortStringHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + IShortStringHelper shortStringHelper, + UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { } diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 102f962ecb..a4d41fcb35 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -13,6 +13,7 @@ using Umbraco.Web.Trees; using Section = Umbraco.Web.Models.ContentEditing.Section; using Umbraco.Web.Models.Trees; using Umbraco.Web.Services; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Editors { @@ -27,8 +28,8 @@ namespace Umbraco.Web.Editors private readonly ITreeService _treeService; public SectionController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, - IDashboardService dashboardService, ISectionService sectionService, ITreeService treeService, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + IDashboardService dashboardService, ISectionService sectionService, ITreeService treeService, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _dashboardService = dashboardService; _sectionService = sectionService; @@ -43,7 +44,7 @@ namespace Umbraco.Web.Editors // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that // since tree's by nature are controllers and require request contextual data - var appTreeController = new ApplicationTreeController(GlobalSettings, UmbracoContextAccessor, SqlContext, Services, AppCaches, Logger, RuntimeState, _treeService, _sectionService, Umbraco) + var appTreeController = new ApplicationTreeController(GlobalSettings, UmbracoContextAccessor, SqlContext, Services, AppCaches, Logger, RuntimeState, _treeService, _sectionService, Umbraco, Mapper) { ControllerContext = ControllerContext }; diff --git a/src/Umbraco.Web/Editors/TemplateController.cs b/src/Umbraco.Web/Editors/TemplateController.cs index 524ecea1eb..92c4b53227 100644 --- a/src/Umbraco.Web/Editors/TemplateController.cs +++ b/src/Umbraco.Web/Editors/TemplateController.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Services; @@ -24,8 +25,8 @@ namespace Umbraco.Web.Editors [UmbracoTreeAuthorize(Constants.Trees.Templates)] public class TemplateController : BackOfficeNotificationsController { - public TemplateController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + public TemplateController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { } diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 09bf8e23d4..193c8391c8 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Services; @@ -36,8 +37,9 @@ namespace Umbraco.Web.Editors IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IVariationContextAccessor variationContextAccessor, - IShortStringHelper shortStringHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + IShortStringHelper shortStringHelper, + UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _variationContextAccessor = variationContextAccessor; } diff --git a/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs b/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs index 4f8876738b..1a432981c8 100644 --- a/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs +++ b/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs @@ -3,6 +3,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Strings; @@ -26,8 +27,8 @@ namespace Umbraco.Web.Editors { } - protected UmbracoAuthorizedJsonController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + protected UmbracoAuthorizedJsonController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { ShortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper)); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 2fc79baa44..8eafcd1fda 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -34,6 +34,7 @@ using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; using IUser = Umbraco.Core.Models.Membership.IUser; using Task = System.Threading.Tasks.Task; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Editors { @@ -55,8 +56,9 @@ namespace Umbraco.Web.Editors IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IMediaFileSystem mediaFileSystem, - IShortStringHelper shortStringHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + IShortStringHelper shortStringHelper, + UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _mediaFileSystem = mediaFileSystem; } diff --git a/src/Umbraco.Web/ExamineExtensions.cs b/src/Umbraco.Web/ExamineExtensions.cs index 9a9fa98d95..421993f8fd 100644 --- a/src/Umbraco.Web/ExamineExtensions.cs +++ b/src/Umbraco.Web/ExamineExtensions.cs @@ -2,32 +2,95 @@ using System.Collections.Generic; using System.Linq; using Examine; -using Umbraco.Core; +using Examine.LuceneEngine.Providers; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Examine; using Umbraco.Web.PublishedCache; namespace Umbraco.Web { /// - /// Extension methods for Examine + /// Extension methods for Examine. /// public static class ExamineExtensions { + /// + /// Creates an containing all content from the . + /// + /// The search results. + /// The cache to fetch the content from. + /// + /// An containing all content. + /// + /// cache + /// + /// Search results are skipped if it can't be fetched from the by its integer id. + /// public static IEnumerable ToPublishedSearchResults(this IEnumerable results, IPublishedCache cache) { - var list = new List(); + if (cache == null) throw new ArgumentNullException(nameof(cache)); - foreach (var result in results.OrderByDescending(x => x.Score)) + var publishedSearchResults = new List(); + + foreach (var result in results) { - if (!int.TryParse(result.Id, out var intId)) continue; //invalid - var content = cache.GetById(intId); - if (content == null) continue; // skip if this doesn't exist in the cache - - list.Add(new PublishedSearchResult(content, result.Score)); - + if (int.TryParse(result.Id, out var contentId) && + cache.GetById(contentId) is IPublishedContent content) + { + publishedSearchResults.Add(new PublishedSearchResult(content, result.Score)); + } } - return list; + return publishedSearchResults; + } + + /// + /// Creates an containing all content, media or members from the . + /// + /// The search results. + /// The snapshot. + /// + /// An containing all content, media or members. + /// + /// snapshot + /// + /// Search results are skipped if it can't be fetched from the respective cache by its integer id. + /// + public static IEnumerable ToPublishedSearchResults(this IEnumerable results, IPublishedSnapshot snapshot) + { + if (snapshot == null) throw new ArgumentNullException(nameof(snapshot)); + + var publishedSearchResults = new List(); + + foreach (var result in results) + { + if (int.TryParse(result.Id, out var contentId) && + result.Values.TryGetValue(LuceneIndex.CategoryFieldName, out var indexType)) + { + IPublishedContent content; + switch (indexType) + { + case IndexTypes.Content: + content = snapshot.Content.GetById(contentId); + break; + case IndexTypes.Media: + content = snapshot.Media.GetById(contentId); + break; + case IndexTypes.Member: + content = snapshot.Members.GetById(contentId); + break; + default: + continue; + } + + if (content != null) + { + publishedSearchResults.Add(new PublishedSearchResult(content, result.Score)); + } + } + } + + return publishedSearchResults; } } } diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 4f4d61b4a8..d71f90aab5 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -8,7 +8,6 @@ using System.Web.Mvc; using System.Web.Mvc.Html; using System.Web.Routing; using Umbraco.Core; -using Umbraco.Core.IO; using Umbraco.Web.Mvc; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; @@ -862,14 +861,14 @@ namespace Umbraco.Web private static readonly HtmlStringUtilities StringUtilities = new HtmlStringUtilities(); /// - /// Replaces text line breaks with HTML line breaks + /// HTML encodes the text and replaces text line breaks with HTML line breaks. /// /// /// The text. - /// The text with text line breaks replaced with HTML line breaks (
)
- public static IHtmlString ReplaceLineBreaksForHtml(this HtmlHelper helper, string text) + /// The HTML encoded text with text line breaks replaced with HTML line breaks (<br />). + public static IHtmlString ReplaceLineBreaks(this HtmlHelper helper, string text) { - return StringUtilities.ReplaceLineBreaksForHtml(text); + return StringUtilities.ReplaceLineBreaks(text); } /// diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 4606a58a3a..8d5e8a9071 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using System.Web; using HtmlAgilityPack; -using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web { @@ -15,17 +13,6 @@ namespace Umbraco.Web /// public sealed class HtmlStringUtilities { - /// - /// Replaces text line breaks with HTML line breaks - /// - /// The text. - /// The text with text line breaks replaced with HTML line breaks (<br />). - [Obsolete("This method doesn't HTML encode the text. Use ReplaceLineBreaks instead.")] - public HtmlString ReplaceLineBreaksForHtml(string text) - { - return new HtmlString(text.Replace("\r\n", @"
").Replace("\n", @"
").Replace("\r", @"
")); - } - /// /// HTML encodes the text and replaces text line breaks with HTML line breaks. /// diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 8a8d678aba..7066475dc9 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -35,52 +35,63 @@ namespace Umbraco.Web /// /// Searches content. /// - /// Term to search. - /// Optional culture. - /// Optional index name. + /// The term to search. + /// The culture (defaults to a culture insensitive search). + /// The name of the index to search (defaults to ). + /// + /// The search results. + /// /// /// - /// When the is not specified or is *, all cultures are searched. + /// When the is not specified or is *, all cultures are searched. /// To search for only invariant documents and fields use null. /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. /// /// While enumerating results, the ambient culture is changed to be the searched culture. /// - IEnumerable Search(string term, string culture = "*", string indexName = null); + IEnumerable Search(string term, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName); /// /// Searches content. /// - /// Term to search. - /// Numbers of items to skip. - /// Numbers of items to return. - /// Total number of matching items. - /// Optional culture. - /// Optional index name. + /// The term to search. + /// The amount of results to skip. + /// The amount of results to take/return. + /// The total amount of records. + /// The culture (defaults to a culture insensitive search). + /// The name of the index to search (defaults to ). + /// + /// The search results. + /// /// /// - /// When the is not specified or is *, all cultures are searched. + /// When the is not specified or is *, all cultures are searched. /// To search for only invariant documents and fields use null. /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. /// /// While enumerating results, the ambient culture is changed to be the searched culture. /// - IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null); + IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName); /// - /// Executes the query and converts the results to PublishedSearchResult. + /// Executes the query and converts the results to . /// - /// - /// While enumerating results, the ambient culture is changed to be the searched culture. - /// + /// The query. + /// + /// The search results. + /// IEnumerable Search(IQueryExecutor query); /// - /// Executes the query and converts the results to PublishedSearchResult. + /// Executes the query and converts the results to . /// - /// - /// While enumerating results, the ambient culture is changed to be the searched culture. - /// + /// The query. + /// The amount of results to skip. + /// The amount of results to take/return. + /// The total amount of records. + /// + /// The search results. + /// IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords); } } diff --git a/src/Umbraco.Web/Install/Models/InstallPackageModel.cs b/src/Umbraco.Web/Install/Models/InstallPackageModel.cs deleted file mode 100644 index 0c7d270033..0000000000 --- a/src/Umbraco.Web/Install/Models/InstallPackageModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Web.Install.Models -{ - // TODO: do we need this? - [Obsolete("This is only used for the obsolete controller InstallPackageController")] - [DataContract(Name = "installPackage", Namespace = "")] - public class InstallPackageModel - { - [DataMember(Name = "kitGuid")] - public Guid KitGuid { get; set; } - [DataMember(Name = "packageId")] - public int PackageId { get; set; } - [DataMember(Name = "packageFile")] - public string PackageFile { get; set; } - - } -} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index cddcfc6d1c..eaaae3616b 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -70,8 +70,14 @@ namespace Umbraco.Web.Models.Mapping dest.Culture = culture; + // Get the segment, which is always allowed to be null even if the propertyType *can* be varied by segment. + // There is therefore no need to perform the null check like with culture above. + var segment = !property.PropertyType.VariesBySegment() ? null : context.GetSegment(); + dest.Segment = segment; + // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. - dest.Value = editor.GetValueEditor().ToEditor(property, culture); + dest.Value = editor.GetValueEditor().ToEditor(property, culture, segment); + } } } diff --git a/src/Umbraco.Web/Profiling/WebProfilingController.cs b/src/Umbraco.Web/Profiling/WebProfilingController.cs index 218ecd0669..fefe52391c 100644 --- a/src/Umbraco.Web/Profiling/WebProfilingController.cs +++ b/src/Umbraco.Web/Profiling/WebProfilingController.cs @@ -2,6 +2,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Strings; @@ -18,8 +19,8 @@ namespace Umbraco.Web.Profiling { private readonly IRuntimeState _runtimeState; - public WebProfilingController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + public WebProfilingController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _runtimeState = runtimeState; } diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index ac5b1dda38..161a73d775 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -158,10 +158,10 @@ namespace Umbraco.Web.PropertyEditors /// public override object ToEditor(IProperty property, string culture = null, string segment = null) { - var val = property.GetValue(culture, segment); - if (val == null) return string.Empty; + var val = property.GetValue(culture, segment)?.ToString(); + if (val.IsNullOrWhiteSpace()) return string.Empty; - var grid = DeserializeGridValue(val.ToString(), out var rtes, out _); + var grid = DeserializeGridValue(val, out var rtes, out _); //process the rte values foreach (var rte in rtes.ToList()) diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueController.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueController.cs index 698b3f46b3..1c41479dcd 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueController.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueController.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Strings; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Core.Mapping; namespace Umbraco.Web.PropertyEditors { @@ -23,8 +24,8 @@ namespace Umbraco.Web.PropertyEditors { private readonly IIOHelper _ioHelper; - public RichTextPreValueController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, IIOHelper ioHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper) + public RichTextPreValueController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper, IIOHelper ioHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, shortStringHelper, umbracoMapper) { _ioHelper = ioHelper; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 4bef41f1eb..49f64a7e1a 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -58,6 +58,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly ContentStore _mediaStore; private readonly SnapDictionary _domainStore; private readonly object _storesLock = new object(); + private readonly object _elementsLock = new object(); private BPlusTree _localContentDb; private BPlusTree _localMediaDb; @@ -1141,7 +1142,13 @@ namespace Umbraco.Web.PublishedCache.NuCache ContentStore.Snapshot contentSnap, mediaSnap; SnapDictionary.Snapshot domainSnap; IAppCache elementsCache; - lock (_storesLock) + + // Here we are reading/writing to shared objects so we need to lock (can't be _storesLock which manages the actual nucache files + // and would result in a deadlock). Even though we are locking around underlying readlocks (within CreateSnapshot) it's because + // we need to ensure that the result of contentSnap.Gen (etc) and the re-assignment of these values and _elements cache + // are done atomically. + + lock (_elementsLock) { var scopeContext = _scopeProvider.Context; diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 2dbe4de4c5..cf145f8d44 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -22,12 +22,6 @@ namespace Umbraco.Web private readonly IVariationContextAccessor _variationContextAccessor; private readonly IExamineManager _examineManager; - [Obsolete("Use the constructor with all parameters instead")] - public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor) - : this (publishedSnapshot, variationContextAccessor, ExamineManager.Instance) - { - } - /// /// Initializes a new instance of the class. /// @@ -183,54 +177,62 @@ namespace Umbraco.Web #region Search /// - public IEnumerable Search(string term, string culture = "*", string indexName = null) + public IEnumerable Search(string term, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName) { return Search(term, 0, 0, out _, culture, indexName); } /// - public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null) + public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = Constants.UmbracoIndexes.ExternalIndexName) { - indexName = string.IsNullOrEmpty(indexName) - ? Constants.UmbracoIndexes.ExternalIndexName - : indexName; + if (skip < 0) + { + throw new ArgumentOutOfRangeException(nameof(skip), skip, "The value must be greater than or equal to zero."); + } + + if (take < 0) + { + throw new ArgumentOutOfRangeException(nameof(take), take, "The value must be greater than or equal to zero."); + } + + if (string.IsNullOrEmpty(indexName)) + { + indexName = Constants.UmbracoIndexes.ExternalIndexName; + } if (!_examineManager.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex)) + { throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}"); + } - var searcher = umbIndex.GetSearcher(); + var query = umbIndex.GetSearcher().CreateQuery(IndexTypes.Content); - // default to max 500 results - var count = skip == 0 && take == 0 ? 500 : skip + take; - - ISearchResults results; + IQueryExecutor queryExecutor; if (culture == "*") { - //search everything - - results = searcher.Search(term, count); + // Search everything + queryExecutor = query.ManagedQuery(term); } - else if (culture.IsNullOrWhiteSpace()) + else if (string.IsNullOrWhiteSpace(culture)) { - //only search invariant - - var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "n"); //must not vary by culture - qry = qry.And().ManagedQuery(term); - results = qry.Execute(count); + // Only search invariant + queryExecutor = query.Field(UmbracoContentIndex.VariesByCultureFieldName, "n") // Must not vary by culture + .And().ManagedQuery(term); } else { - //search only the specified culture - - //get all index fields suffixed with the culture name supplied - var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); - var qry = searcher.CreateQuery().ManagedQuery(term, cultureFields); - results = qry.Execute(count); + // Only search the specified culture + var fields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); // Get all index fields suffixed with the culture name supplied + queryExecutor = query.ManagedQuery(term, fields); } + var results = skip == 0 && take == 0 + ? queryExecutor.Execute() + : queryExecutor.Execute(skip + take); + totalRecords = results.TotalItemCount; - return new CultureContextualSearchResults(results.ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture); + return new CultureContextualSearchResults(results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot.Content), _variationContextAccessor, culture); } /// @@ -242,12 +244,23 @@ namespace Umbraco.Web /// public IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords) { + if (skip < 0) + { + throw new ArgumentOutOfRangeException(nameof(skip), skip, "The value must be greater than or equal to zero."); + } + + if (take < 0) + { + throw new ArgumentOutOfRangeException(nameof(take), take, "The value must be greater than or equal to zero."); + } + var results = skip == 0 && take == 0 ? query.Execute() - : query.Execute(maxResults: skip + take); + : query.Execute(skip + take); totalRecords = results.TotalItemCount; - return results.ToPublishedSearchResults(_publishedSnapshot.Content); + + return results.Skip(skip).ToPublishedSearchResults(_publishedSnapshot); } /// @@ -320,9 +333,6 @@ namespace Umbraco.Web } } - - - #endregion } } diff --git a/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs index eae198bb59..7dc8203d40 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByConfigured404.cs @@ -1,3 +1,4 @@ +using Examine; using System.Globalization; using System.Linq; using Umbraco.Core; @@ -16,12 +17,14 @@ namespace Umbraco.Web.Routing private readonly ILogger _logger; private readonly IEntityService _entityService; private readonly IContentSection _contentConfigSection; + private readonly IExamineManager _examineManager; - public ContentFinderByConfigured404(ILogger logger, IEntityService entityService, IContentSection contentConfigSection) + public ContentFinderByConfigured404(ILogger logger, IEntityService entityService, IContentSection contentConfigSection, IExamineManager examineManager) { _logger = logger; _entityService = entityService; _contentConfigSection = contentConfigSection; + _examineManager = examineManager; } /// @@ -62,7 +65,7 @@ namespace Umbraco.Web.Routing var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( _contentConfigSection.Error404Collection.ToArray(), _entityService, - new PublishedContentQuery(frequest.UmbracoContext.PublishedSnapshot, frequest.UmbracoContext.VariationContextAccessor), + new PublishedContentQuery(frequest.UmbracoContext.PublishedSnapshot, frequest.UmbracoContext.VariationContextAccessor, _examineManager), errorCulture); IPublishedContent content = null; diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 0c87ac78cb..e2d2e59157 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -106,7 +106,7 @@ namespace Umbraco.Web.Runtime composition.Register(factory => { var umbCtx = factory.GetInstance(); - return new PublishedContentQuery(umbCtx.UmbracoContext.PublishedSnapshot, factory.GetInstance()); + return new PublishedContentQuery(umbCtx.UmbracoContext.PublishedSnapshot, factory.GetInstance(), factory.GetInstance()); }, Lifetime.Request); composition.Register(Lifetime.Request); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index ab5aa364a3..416513d512 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -118,10 +118,6 @@ namespace Umbraco.Web.Search public void Terminate() { } - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("This method should not be used and will be removed in future versions, rebuilding indexes can be done with the IndexRebuilder or the BackgroundIndexRebuilder")] - public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) => Current.Factory.GetInstance().RebuildIndexes(onlyEmptyIndexes, waitMilliseconds); - #region Cache refresher updated event handlers /// diff --git a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs index aad8303f40..71b5bee108 100644 --- a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs @@ -72,7 +72,7 @@ namespace Umbraco.Web.Search IOHelper, RuntimeState, LanguageService, - GetContentValueSetValidator() + UmbracoIndexConfig.GetContentValueSetValidator() ); return index; } @@ -88,7 +88,7 @@ namespace Umbraco.Web.Search IOHelper, RuntimeState, LanguageService, - GetPublishedContentValueSetValidator()); + UmbracoIndexConfig.GetPublishedContentValueSetValidator()); return index; } @@ -102,31 +102,9 @@ namespace Umbraco.Web.Search ProfilingLogger, IOHelper, RuntimeState, - GetMemberValueSetValidator() + UmbracoIndexConfig.GetMemberValueSetValidator() ); return index; } - [Obsolete("This method should not be used and will be removed in future versions. GetContentValueSetValidator was moved to IUmbracoIndexConfig")] - public virtual IContentValueSetValidator GetContentValueSetValidator() - { - return UmbracoIndexConfig.GetContentValueSetValidator(); - } - [Obsolete("This method should not be used and will be removed in future versions. GetPublishedContentValueSetValidator was moved to IUmbracoIndexConfig")] - public virtual IContentValueSetValidator GetPublishedContentValueSetValidator() - { - return UmbracoIndexConfig.GetPublishedContentValueSetValidator(); - } - - /// - /// Returns the for the member indexer - /// - /// - [Obsolete("This method should not be used and will be removed in future versions. GetMemberValueSetValidator was moved to IUmbracoIndexConfig")] - public virtual IValueSetValidator GetMemberValueSetValidator() - { - return UmbracoIndexConfig.GetMemberValueSetValidator(); - } - - } } diff --git a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs deleted file mode 100644 index 12d7cce753..0000000000 --- a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using Umbraco.Web.Mvc; -using Umbraco.Core; -using System.Web.Helpers; -using System.Web; -using Newtonsoft.Json; -using System.ComponentModel; - -namespace Umbraco.Web.Security -{ - [Obsolete("This is no longer used and will be removed from the codebase in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider - { - private readonly IAntiForgeryAdditionalDataProvider _defaultProvider; - - /// - /// Constructor, allows wrapping a default provider - /// - /// - public UmbracoAntiForgeryAdditionalDataProvider(IAntiForgeryAdditionalDataProvider defaultProvider) - { - _defaultProvider = defaultProvider; - } - - public string GetAdditionalData(HttpContextBase context) - { - return JsonConvert.SerializeObject(new AdditionalData - { - Stamp = DateTime.UtcNow.Ticks, - //this value will be here if this is a BeginUmbracoForms form - Ufprt = context.Items["ufprt"]?.ToString(), - //if there was a wrapped provider, add it's value to the json, else just a static value - WrappedValue = _defaultProvider?.GetAdditionalData(context) ?? "default" - }); - } - - public bool ValidateAdditionalData(HttpContextBase context, string additionalData) - { - if (!additionalData.DetectIsJson()) - return false; //must be json - - AdditionalData json; - try - { - json = JsonConvert.DeserializeObject(additionalData); - } - catch - { - return false; //couldn't parse - } - - if (json.Stamp == default) return false; - - //if there was a wrapped provider, validate it, else validate the static value - var validateWrapped = _defaultProvider?.ValidateAdditionalData(context, json.WrappedValue) ?? json.WrappedValue == "default"; - if (!validateWrapped) - return false; - - var ufprtRequest = context.Request["ufprt"]?.ToString(); - - //if the custom BeginUmbracoForms route value is not there, then it's nothing more to validate - if (ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) - return true; - - //if one or the other is null then something is wrong - if (!ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) return false; - if (ufprtRequest.IsNullOrWhiteSpace() && !json.Ufprt.IsNullOrWhiteSpace()) return false; - - if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(json.Ufprt, out var additionalDataParts)) - return false; - - if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprtRequest, out var requestParts)) - return false; - - //ensure they all match - return additionalDataParts.Count == requestParts.Count - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Action] - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Area]; - } - - internal class AdditionalData - { - public string Ufprt { get; set; } - public long Stamp { get; set; } - public string WrappedValue { get; set; } - } - - } -} diff --git a/src/Umbraco.Web/Suspendable.cs b/src/Umbraco.Web/Suspendable.cs index 619f6d7eda..a1d79d3619 100644 --- a/src/Umbraco.Web/Suspendable.cs +++ b/src/Umbraco.Web/Suspendable.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Logging; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Web.Composing; using Umbraco.Examine; using Umbraco.Web.Cache; @@ -78,8 +79,7 @@ namespace Umbraco.Web if (_tried == false) return; _tried = false; - // TODO: when resuming do we always want a full rebuild of all indexes? - ExamineComponent.RebuildIndexes(indexRebuilder, logger, false); + Current.Factory.GetInstance().RebuildIndexes(false); } } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index f0a860496e..dea6642376 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -13,6 +13,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; @@ -36,8 +37,8 @@ namespace Umbraco.Web.Trees public ApplicationTreeController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, - IRuntimeState runtimeState, ITreeService treeService, ISectionService sectionService, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + IRuntimeState runtimeState, ITreeService treeService, ISectionService sectionService, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { _treeService = treeService; _sectionService = sectionService; diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 1a56642a74..14612de02d 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -19,6 +19,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Trees { @@ -50,7 +51,7 @@ namespace Umbraco.Web.Trees protected override int[] UserStartNodes => _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService)); - public ContentTreeController(UmbracoTreeSearcher treeSearcher, ActionCollection actions, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public ContentTreeController(UmbracoTreeSearcher treeSearcher, ActionCollection actions, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { _treeSearcher = treeSearcher; _actions = actions; diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 9b43f54130..7642ee103e 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -21,13 +21,14 @@ using Umbraco.Core.Security; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Trees { public abstract class ContentTreeControllerBase : TreeController { - protected ContentTreeControllerBase(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + protected ContentTreeControllerBase(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { } @@ -195,20 +196,30 @@ namespace Umbraco.Web.Trees //get the current user start node/paths GetUserStartNodes(out var userStartNodes, out var userStartNodePaths); - nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); - // if the user does not have access to the root node, what we have is the start nodes, - // but to provide some context we also need to add their topmost nodes when they are not + // but to provide some context we need to add their topmost nodes when they are not // topmost nodes themselves (level > 1). if (id == rootIdString && hasAccessToRoot == false) { - var topNodeIds = entities.Where(x => x.Level > 1).Select(GetTopNodeId).Where(x => x != 0).Distinct().ToArray(); + // first add the entities that are topmost to the nodes collection + var topMostEntities = entities.Where(x => x.Level == 1).ToArray(); + nodes.AddRange(topMostEntities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); + + // now add the topmost nodes of the entities that aren't topmost to the nodes collection as well + // - these will appear as "no-access" nodes in the tree, but will allow the editors to drill down through the tree + // until they reach their start nodes + var topNodeIds = entities.Except(topMostEntities).Select(GetTopNodeId).Where(x => x != 0).Distinct().ToArray(); if (topNodeIds.Length > 0) { var topNodes = Services.EntityService.GetAll(UmbracoObjectType, topNodeIds.ToArray()); nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); } } + else + { + // the user has access to the root, just add the entities + nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); + } return nodes; } diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index e8f8fb9f75..a932fbc594 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -6,6 +6,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; @@ -26,7 +27,7 @@ namespace Umbraco.Web.Trees { private readonly UmbracoTreeSearcher _treeSearcher; - public ContentTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public ContentTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { _treeSearcher = treeSearcher; } diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index b39feeae6c..f9b5dd729d 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Web.Search; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Trees { @@ -28,7 +29,7 @@ namespace Umbraco.Web.Trees { private readonly UmbracoTreeSearcher _treeSearcher; - public DataTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public DataTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { _treeSearcher = treeSearcher; } @@ -60,19 +61,20 @@ namespace Umbraco.Web.Trees //System ListView nodes var systemListViewDataTypeIds = GetNonDeletableSystemListViewDataTypeIds(); + var children = Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DataType).ToArray(); + var dataTypes = Services.DataTypeService.GetAll(children.Select(c => c.Id).ToArray()).ToDictionary(dt => dt.Id); + nodes.AddRange( - Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.DataType) + children .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, Constants.Icons.DataType, false); + var dataType = dataTypes[dt.Id]; + var node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, dataType.Editor.Icon, false); node.Path = dt.Path; - if (systemListViewDataTypeIds.Contains(dt.Id)) - { - node.Icon = Constants.Icons.ListView; - } return node; - })); + }) + ); return nodes; } diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index d3ca44a0ac..dfe1b5cb5a 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -20,6 +20,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Trees { @@ -39,7 +40,7 @@ namespace Umbraco.Web.Trees { private readonly UmbracoTreeSearcher _treeSearcher; - public MediaTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public MediaTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { _treeSearcher = treeSearcher; } diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index f85aefcace..db659d7939 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Web.Search; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Trees { @@ -26,7 +27,7 @@ namespace Umbraco.Web.Trees { private readonly UmbracoTreeSearcher _treeSearcher; - public MediaTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public MediaTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { _treeSearcher = treeSearcher; } diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index d0ca8d1b2e..d7247208c7 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -6,6 +6,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; @@ -28,7 +29,7 @@ namespace Umbraco.Web.Trees { private readonly UmbracoTreeSearcher _treeSearcher; - public TemplatesTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public TemplatesTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { _treeSearcher = treeSearcher; } diff --git a/src/Umbraco.Web/Trees/TreeController.cs b/src/Umbraco.Web/Trees/TreeController.cs index be862a8390..b580ebdb34 100644 --- a/src/Umbraco.Web/Trees/TreeController.cs +++ b/src/Umbraco.Web/Trees/TreeController.cs @@ -4,6 +4,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Persistence; using Umbraco.Core.Services; @@ -18,8 +19,8 @@ namespace Umbraco.Web.Trees private readonly TreeAttribute _treeAttribute; - protected TreeController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + protected TreeController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { _treeAttribute = GetTreeAttribute(); } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 77e5ebef62..a0a736f68a 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -16,6 +16,7 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Services; +using Umbraco.Core.Mapping; namespace Umbraco.Web.Trees { @@ -32,8 +33,8 @@ namespace Umbraco.Web.Trees { } - protected TreeControllerBase(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + protected TreeControllerBase(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ae1c196a21..4e11601750 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -62,7 +62,7 @@ - + 2.7.0.100 @@ -81,7 +81,7 @@ - 1.0.0-beta2-19324-01 + 1.0.0-beta2-19554-01 runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -140,7 +140,6 @@ - @@ -333,7 +332,6 @@ - @@ -599,7 +597,6 @@ - diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index ea8a6d5481..3b00c67b42 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -116,23 +116,11 @@ namespace Umbraco.Web /// public IPublishedSnapshot PublishedSnapshot => _publishedSnapshot.Value; - /// - /// Gets the published content cache. - /// - [Obsolete("Use the Content property.")] - public IPublishedContentCache ContentCache => PublishedSnapshot.Content; - /// /// Gets the published content cache. /// public IPublishedContentCache Content => PublishedSnapshot.Content; - /// - /// Gets the published media cache. - /// - [Obsolete("Use the Media property.")] - public IPublishedMediaCache MediaCache => PublishedSnapshot.Media; - /// /// Gets the published media cache. /// @@ -247,30 +235,6 @@ namespace Umbraco.Web return UrlProvider.GetUrl(contentId, mode, culture); } - /// - /// Gets the absolute url of a content identified by its identifier. - /// - /// The content identifier. - /// - /// The absolute url for the content. - [Obsolete("Use the Url() method with UrlMode.Absolute.")] - public string UrlAbsolute(int contentId, string culture = null) - { - return UrlProvider.GetUrl(contentId, UrlMode.Absolute, culture); - } - - /// - /// Gets the absolute url of a content identified by its identifier. - /// - /// The content identifier. - /// - /// The absolute url for the content. - [Obsolete("Use the Url() method with UrlMode.Absolute.")] - public string UrlAbsolute(Guid contentId, string culture = null) - { - return UrlProvider.GetUrl(contentId, UrlMode.Absolute, culture); - } - #endregion private string PreviewToken diff --git a/src/Umbraco.Web/WebApi/UmbracoApiController.cs b/src/Umbraco.Web/WebApi/UmbracoApiController.cs index 4600c02ac1..37ccbaec28 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiController.cs @@ -19,12 +19,6 @@ namespace Umbraco.Web.WebApi { } - [Obsolete("This constructor is obsolete since it doesn't inject the UmbracoMapper. The UmbracoMapper will be resolved from the service locator Current.Mapper, which is not good for testability. Inject the UmbracoMapper using full constructor injection instead.")] - protected UmbracoApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) - { - } - protected UmbracoApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs index cebb5306c9..f019ffe2df 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiControllerBase.cs @@ -61,22 +61,6 @@ namespace Umbraco.Web.WebApi Mapper = umbracoMapper; } - [Obsolete("This constructor is obsolete since it doesn't inject the UmbracoMapper. The UmbracoMapper will be resolved from the service locator Current.Mapper, which is not good for testability. Inject the UmbracoMapper using full constructor injection instead.")] - protected UmbracoApiControllerBase(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - { - UmbracoContextAccessor = umbracoContextAccessor; - GlobalSettings = globalSettings; - SqlContext = sqlContext; - Services = services; - AppCaches = appCaches; - Logger = logger; - RuntimeState = runtimeState; - Umbraco = umbracoHelper; - - // not good for testability, hence the obsolete. - Mapper = Current.Mapper; - } - /// /// Gets a unique instance identifier. /// diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index cf9707d40b..f41b4cd01a 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Models.Identity; using Umbraco.Web.Security; +using Umbraco.Core.Mapping; namespace Umbraco.Web.WebApi { @@ -35,8 +36,8 @@ namespace Umbraco.Web.WebApi { } - protected UmbracoAuthorizedApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) - : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + protected UmbracoAuthorizedApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) + : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { } diff --git a/src/umbraco.sln b/src/umbraco.sln index 96897e0d27..0e060fb4d3 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -110,6 +110,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Configuration", "Um EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Infrastrucure.Persistance.SqlCe", "Umbraco.Infrastrucure.Persistance.SqlCe\Umbraco.Infrastrucure.Persistance.SqlCe.csproj", "{33085570-9BF2-4065-A9B0-A29D920D13BA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.TestData", "Umbraco.TestData\Umbraco.TestData.csproj", "{FB5676ED-7A69-492C-B802-E7B24144C0FC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -158,6 +160,10 @@ Global {33085570-9BF2-4065-A9B0-A29D920D13BA}.Debug|Any CPU.Build.0 = Debug|Any CPU {33085570-9BF2-4065-A9B0-A29D920D13BA}.Release|Any CPU.ActiveCfg = Release|Any CPU {33085570-9BF2-4065-A9B0-A29D920D13BA}.Release|Any CPU.Build.0 = Release|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB5676ED-7A69-492C-B802-E7B24144C0FC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -170,6 +176,7 @@ Global {53594E5B-64A2-4545-8367-E3627D266AE8} = {FD962632-184C-4005-A5F3-E705D92FC645} {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645} + {FB5676ED-7A69-492C-B802-E7B24144C0FC} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC}