diff --git a/.github/BUILD.md b/.github/BUILD.md index a9c26f3a9b..c6e870f396 100644 --- a/.github/BUILD.md +++ b/.github/BUILD.md @@ -39,6 +39,8 @@ To build Umbraco, fire up PowerShell and move to Umbraco's repository root (the build/build.ps1 +If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (dev-v8) the file will appear and you can build it. + You might run into [Powershell quirks](#powershell-quirks). ### Build Infrastructure @@ -209,4 +211,4 @@ The best solution is to unblock the Zip file before un-zipping: right-click the ### Git Quirks -Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details). \ No newline at end of file +Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details). diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2679eaa411..e009ee2294 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -59,7 +59,8 @@ Great question! The short version goes like this: * **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool ![Clone the fork](img/clonefork.png) - + + * **Switch to the correct branch** - switch to the v8-dev branch * **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md) * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions) * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/dev`, create a new branch first. diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt index ba88f808c0..91d49d896c 100644 --- a/build/NuSpecs/tools/ReadmeUpgrade.txt +++ b/build/NuSpecs/tools/ReadmeUpgrade.txt @@ -11,17 +11,12 @@ Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P Don't forget to build! -We've done our best to transform your configuration files but in case something is not quite right: remember we -backed up your files in App_Data\NuGetBackup so you can find the original files before they were transformed. - -We've overwritten all the files in the Umbraco folder, these have been backed up in -App_Data\NuGetBackup. We didn't overwrite the UI.xml file nor did we remove any files or folders that you or -a package might have added. Only the existing files were overwritten. If you customized anything then make -sure to do a compare and merge with the NuGetBackup folder. +We've done our best to transform your configuration files but in case something is not quite right: we recommmend you look in source control for the previous version so you can find the original files before they were transformed. This NuGet package includes build targets that extend the creation of a deploy package, which is generated by Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use Publish this won't affect you. + The following items will now be automatically included when creating a deploy package or publishing to the file system: umbraco, config\splashes and global.asax. diff --git a/build/build.ps1 b/build/build.ps1 index 892654d3cd..ea07e4516f 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -456,10 +456,10 @@ $ubuild.DefineMethod("PrepareAngularDocs", { Write-Host "Prepare Angular Documentation" - + $src = "$($this.SolutionRoot)\src" $out = $this.BuildOutput - + "Moving to Umbraco.Web.UI.Docs folder" cd ..\src\Umbraco.Web.UI.Docs diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 5614cb8c41..363677b826 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.4.0")] -[assembly: AssemblyInformationalVersion("8.4.0")] +[assembly: AssemblyFileVersion("8.6.0")] +[assembly: AssemblyInformationalVersion("8.6.0")] diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index 54367ed588..c4dba51acd 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -70,7 +70,23 @@ namespace Umbraco.Core.Collections /// The number of elements contained in the . /// /// 2 - public int Count => GetThreadSafeClone().Count; + public int Count + { + get + { + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Count; + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } + + } + } /// /// Gets a value indicating whether the is read-only. @@ -105,8 +121,7 @@ namespace Umbraco.Core.Collections /// public bool TryAdd(T item) { - var clone = GetThreadSafeClone(); - if (clone.Contains(item)) return false; + if (Contains(item)) return false; try { _instanceLocker.EnterWriteLock(); @@ -150,7 +165,16 @@ namespace Umbraco.Core.Collections /// The object to locate in the . public bool Contains(T item) { - return GetThreadSafeClone().Contains(item); + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Contains(item); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } } /// @@ -168,13 +192,13 @@ namespace Umbraco.Core.Collections HashSet clone = null; try { - _instanceLocker.EnterWriteLock(); + _instanceLocker.EnterReadLock(); clone = new HashSet(_innerSet, _innerSet.Comparer); } finally { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); } return clone; } diff --git a/src/Umbraco.Core/Collections/ObservableDictionary.cs b/src/Umbraco.Core/Collections/ObservableDictionary.cs index 40269aa4eb..c6aedab377 100644 --- a/src/Umbraco.Core/Collections/ObservableDictionary.cs +++ b/src/Umbraco.Core/Collections/ObservableDictionary.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Runtime.Serialization; namespace Umbraco.Core.Collections { @@ -26,7 +27,7 @@ namespace Umbraco.Core.Collections /// The equality comparer to use when comparing keys, or null to use the default comparer. public ObservableDictionary(Func keySelector, IEqualityComparer equalityComparer = null) { - KeySelector = keySelector ?? throw new ArgumentException("keySelector"); + KeySelector = keySelector ?? throw new ArgumentException(nameof(keySelector)); Indecies = new Dictionary(equalityComparer); } @@ -36,7 +37,7 @@ namespace Umbraco.Core.Collections { var key = KeySelector(item); if (Indecies.ContainsKey(key)) - throw new DuplicateKeyException(key.ToString()); + throw new ArgumentException($"An element with the same key '{key}' already exists in the dictionary.", nameof(item)); if (index != Count) { @@ -91,7 +92,7 @@ namespace Umbraco.Core.Collections { //confirm key matches if (!KeySelector(value).Equals(key)) - throw new InvalidOperationException("Key of new value does not match"); + throw new InvalidOperationException("Key of new value does not match."); if (!Indecies.ContainsKey(key)) { @@ -118,7 +119,7 @@ namespace Umbraco.Core.Collections //confirm key matches if (!KeySelector(value).Equals(key)) - throw new InvalidOperationException("Key of new value does not match"); + throw new InvalidOperationException("Key of new value does not match."); this[Indecies[key]] = value; return true; @@ -155,12 +156,12 @@ namespace Umbraco.Core.Collections { if (!Indecies.ContainsKey(currentKey)) { - throw new InvalidOperationException("No item with the key " + currentKey + "was found in the collection"); + throw new InvalidOperationException($"No item with the key '{currentKey}' was found in the dictionary."); } if (ContainsKey(newKey)) { - throw new DuplicateKeyException(newKey.ToString()); + throw new ArgumentException($"An element with the same key '{newKey}' already exists in the dictionary.", nameof(newKey)); } var currentIndex = Indecies[currentKey]; @@ -234,16 +235,5 @@ namespace Umbraco.Core.Collections } #endregion - - internal class DuplicateKeyException : Exception - { - public DuplicateKeyException(string key) - : base("Attempted to insert duplicate key \"" + key + "\" in collection.") - { - Key = key; - } - - public string Key { get; } - } } } diff --git a/src/Umbraco.Core/Composing/Composers.cs b/src/Umbraco.Core/Composing/Composers.cs index 0510740e42..b2e6c9d068 100644 --- a/src/Umbraco.Core/Composing/Composers.cs +++ b/src/Umbraco.Core/Composing/Composers.cs @@ -18,19 +18,52 @@ namespace Umbraco.Core.Composing private readonly Composition _composition; private readonly IProfilingLogger _logger; private readonly IEnumerable _composerTypes; + private readonly IEnumerable _enableDisableAttributes; private const int LogThresholdMilliseconds = 100; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The composition. - /// The composer types. - /// A profiling logger. + /// 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. + /// + /// The composition. + /// The types. + /// The and/or attributes. + /// The profiling logger. + /// composition + /// or + /// composerTypes + /// or + /// enableDisableAttributes + /// or + /// logger + + public Composers(Composition composition, IEnumerable composerTypes, IEnumerable enableDisableAttributes, IProfilingLogger logger) { _composition = composition ?? throw new ArgumentNullException(nameof(composition)); _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes)); + _enableDisableAttributes = enableDisableAttributes ?? throw new ArgumentNullException(nameof(enableDisableAttributes)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -103,7 +136,7 @@ namespace Umbraco.Core.Composing .ToList(); // enable or disable composers - EnableDisableComposers(composerTypeList); + EnableDisableComposers(_enableDisableAttributes, composerTypeList); void GatherInterfaces(Type type, Func getTypeInAttribute, HashSet iset, List set2) where TAttribute : Attribute @@ -218,7 +251,7 @@ namespace Umbraco.Core.Composing return text.ToString(); } - private static void EnableDisableComposers(ICollection types) + private static void EnableDisableComposers(IEnumerable enableDisableAttributes, ICollection types) { var enabled = new Dictionary(); @@ -240,20 +273,16 @@ namespace Umbraco.Core.Composing enableInfo.Weight = weight2; } - var assemblies = types.Select(x => x.Assembly).Distinct(); - foreach (var assembly in assemblies) + foreach (var attr in enableDisableAttributes.OfType()) { - foreach (var attr in assembly.GetCustomAttributes()) - { - var type = attr.EnabledType; - UpdateEnableInfo(type, 2, enabled, true); - } + var type = attr.EnabledType; + UpdateEnableInfo(type, 2, enabled, true); + } - foreach (var attr in assembly.GetCustomAttributes()) - { - var type = attr.DisabledType; - UpdateEnableInfo(type, 2, enabled, false); - } + foreach (var attr in enableDisableAttributes.OfType()) + { + var type = attr.DisabledType; + UpdateEnableInfo(type, 2, enabled, false); } foreach (var composerType in types) diff --git a/src/Umbraco.Core/Composing/LightInject/LightInjectException.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectException.cs index fa0aed21ca..e1344468f9 100644 --- a/src/Umbraco.Core/Composing/LightInject/LightInjectException.cs +++ b/src/Umbraco.Core/Composing/LightInject/LightInjectException.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.Serialization; using System.Text; namespace Umbraco.Core.Composing.LightInject @@ -6,20 +7,51 @@ namespace Umbraco.Core.Composing.LightInject /// /// Represents errors that occur due to LightInject. /// + /// + [Serializable] public class LightInjectException : Exception { - public LightInjectException(string message) - : base(message) - { } - - public LightInjectException(string message, Exception innerException) - : base(message, innerException) - { } - private const string LightInjectUnableToResolveType = "Unable to resolve type:"; private const string LightInjectUnresolvedDependency = "Unresolved dependency "; private const string LightInjectRequestedDependency = "[Requested dependency:"; + /// + /// Initializes a new instance of the class. + /// + public LightInjectException() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public LightInjectException(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 LightInjectException(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 LightInjectException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + + /// + /// Tries to throw the exception with additional details. + /// + /// The exception. + /// public static void TryThrow(Exception e) { var ex = e as InvalidOperationException; @@ -32,6 +64,12 @@ namespace Umbraco.Core.Composing.LightInject throw new LightInjectException(sb.ToString(), e); } + /// + /// Tries to throw the exception with additional details. + /// + /// The exception. + /// The implementing type. + /// public static void TryThrow(Exception e, Type implementingType) { var ex = e as InvalidOperationException; @@ -45,6 +83,11 @@ namespace Umbraco.Core.Composing.LightInject throw new LightInjectException(sb.ToString(), e); } + /// + /// Writes the details. + /// + /// The exception. + /// The to write the details to. private static void WriteDetails(InvalidOperationException ex, StringBuilder sb) { ex = ex.InnerException as InvalidOperationException; diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index fe7a561eca..6d0b1a0514 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Web; @@ -505,6 +506,49 @@ namespace Umbraco.Core.Composing #endregion + #region Get Assembly Attributes + + /// + /// Gets the assembly attributes of the specified type . + /// + /// The attribute type. + /// + /// The assembly attributes of the specified type . + /// + public IEnumerable GetAssemblyAttributes() + where T : Attribute + { + return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + } + + /// + /// Gets all the assembly attributes. + /// + /// + /// All assembly attributes. + /// + public IEnumerable GetAssemblyAttributes() + { + return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + } + + /// + /// Gets the assembly attributes of the specified . + /// + /// The attribute types. + /// + /// The assembly attributes of the specified types. + /// + /// attributeTypes + public IEnumerable GetAssemblyAttributes(params Type[] attributeTypes) + { + if (attributeTypes == null) throw new ArgumentNullException(nameof(attributeTypes)); + + return AssembliesToScan.SelectMany(a => attributeTypes.SelectMany(at => a.GetCustomAttributes(at))).ToList(); + } + + #endregion + #region Get Types /// @@ -813,11 +857,44 @@ namespace Umbraco.Core.Composing } /// - /// Represents the error that occurs when a type was not found in the cache type - /// list with the specified TypeResolutionKind. + /// Represents the error that occurs when a type was not found in the cache type list with the specified TypeResolutionKind. /// + /// + [Serializable] internal class CachedTypeNotFoundInFileException : Exception - { } + { + /// + /// Initializes a new instance of the class. + /// + public CachedTypeNotFoundInFileException() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public CachedTypeNotFoundInFileException(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 CachedTypeNotFoundInFileException(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 CachedTypeNotFoundInFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } #endregion } diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index e2e0f30874..c1d7103a1c 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -221,7 +221,8 @@ namespace Umbraco.Core FailedPasswordAttempts, new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Integer, true, FailedPasswordAttempts) { - Name = FailedPasswordAttemptsLabel + Name = FailedPasswordAttemptsLabel, + DataTypeId = Constants.DataTypes.LabelInt } }, { @@ -242,35 +243,40 @@ namespace Umbraco.Core LastLockoutDate, new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastLockoutDate) { - Name = LastLockoutDateLabel + Name = LastLockoutDateLabel, + DataTypeId = Constants.DataTypes.LabelDateTime } }, { LastLoginDate, new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastLoginDate) { - Name = LastLoginDateLabel + Name = LastLoginDateLabel, + DataTypeId = Constants.DataTypes.LabelDateTime } }, { LastPasswordChangeDate, new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Date, true, LastPasswordChangeDate) { - Name = LastPasswordChangeDateLabel + Name = LastPasswordChangeDateLabel, + DataTypeId = Constants.DataTypes.LabelDateTime } }, { PasswordAnswer, new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, PasswordAnswer) { - Name = PasswordAnswerLabel + Name = PasswordAnswerLabel, + DataTypeId = Constants.DataTypes.LabelString } }, { PasswordQuestion, new PropertyType(PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar, true, PasswordQuestion) { - Name = PasswordQuestionLabel + Name = PasswordQuestionLabel, + DataTypeId = Constants.DataTypes.LabelString } } }; diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index a7d40b0b7d..3edad0c963 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -13,6 +13,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; @@ -23,6 +24,8 @@ namespace Umbraco.Core // this ain't pretty private static IMediaFileSystem _mediaFileSystem; private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem); + private static readonly PropertyEditorCollection _propertyEditors; + private static PropertyEditorCollection PropertyEditors = _propertyEditors ?? (_propertyEditors = Current.PropertyEditors); #region IContent @@ -162,14 +165,12 @@ namespace Umbraco.Core // Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an // existing IMedia with extension SetValue causes exception 'Illegal characters in path' string oldpath = null; - if (property.GetValue(culture, segment) is string svalue) + var value = property.GetValue(culture, segment); + + if (PropertyEditors.TryGet(propertyTypeAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) { - if (svalue.DetectIsJson()) - { - // the property value is a JSON serialized image crop data set - grab the "src" property as the file source - var jObject = JsonConvert.DeserializeObject(svalue); - svalue = jObject != null ? jObject.GetValueAsString("src") : svalue; - } + var svalue = dataEditor.GetMediaPath(value); oldpath = MediaFileSystem.GetRelativePath(svalue); } diff --git a/src/Umbraco.Core/EnumExtensions.cs b/src/Umbraco.Core/EnumExtensions.cs index 1443a27876..b2e5f32c9a 100644 --- a/src/Umbraco.Core/EnumExtensions.cs +++ b/src/Umbraco.Core/EnumExtensions.cs @@ -41,5 +41,43 @@ namespace Umbraco.Core return (num & nums) > 0; } + + /// + /// Sets a flag of the given input enum + /// + /// + /// Enum to set flag of + /// Flag to set + /// A new enum with the flag set + public static T SetFlag(this T input, T flag) + where T : Enum + { + var i = Convert.ToUInt64(input); + var f = Convert.ToUInt64(flag); + + // bitwise OR to set flag f of enum i + var result = i | f; + + return (T)Enum.ToObject(typeof(T), result); + } + + /// + /// Unsets a flag of the given input enum + /// + /// + /// Enum to unset flag of + /// Flag to unset + /// A new enum with the flag unset + public static T UnsetFlag(this T input, T flag) + where T : Enum + { + var i = Convert.ToUInt64(input); + var f = Convert.ToUInt64(flag); + + // bitwise AND combined with bitwise complement to unset flag f of enum i + var result = i & ~f; + + return (T)Enum.ToObject(typeof(T), result); + } } } diff --git a/src/Umbraco.Core/Exceptions/ArgumentNullOrEmptyException.cs b/src/Umbraco.Core/Exceptions/ArgumentNullOrEmptyException.cs index 90cc20c404..037d35d0ee 100644 --- a/src/Umbraco.Core/Exceptions/ArgumentNullOrEmptyException.cs +++ b/src/Umbraco.Core/Exceptions/ArgumentNullOrEmptyException.cs @@ -1,16 +1,24 @@ 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. + /// 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 - /// with the name of the parameter that caused this exception. + /// 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) @@ -18,13 +26,30 @@ namespace Umbraco.Core.Exceptions { } /// - /// Initializes a new instance of the class - /// with a specified error message and the name of the parameter that caused this exception. + /// 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.Core/Exceptions/AuthorizationException.cs b/src/Umbraco.Core/Exceptions/AuthorizationException.cs index 955fec270b..b87a8da8b8 100644 --- a/src/Umbraco.Core/Exceptions/AuthorizationException.cs +++ b/src/Umbraco.Core/Exceptions/AuthorizationException.cs @@ -1,14 +1,45 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Core.Exceptions { + /// + /// The exception that is thrown when authorization failed. + /// + /// + [Serializable] public class AuthorizationException : Exception { + /// + /// Initializes a new instance of the class. + /// public AuthorizationException() { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. public AuthorizationException(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 AuthorizationException(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 AuthorizationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } } diff --git a/src/Umbraco.Core/Exceptions/BootFailedException.cs b/src/Umbraco.Core/Exceptions/BootFailedException.cs index c3262d26c6..e8ffe1d2e9 100644 --- a/src/Umbraco.Core/Exceptions/BootFailedException.cs +++ b/src/Umbraco.Core/Exceptions/BootFailedException.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.Serialization; using System.Text; namespace Umbraco.Core.Exceptions @@ -6,6 +7,8 @@ namespace Umbraco.Core.Exceptions /// /// An exception that is thrown if the Umbraco application cannot boot. /// + /// + [Serializable] public class BootFailedException : Exception { /// @@ -14,27 +17,47 @@ namespace Umbraco.Core.Exceptions public const string DefaultMessage = "Boot failed: Umbraco cannot run. See Umbraco's log file for more details."; /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class. /// - /// The message that describes the error. + public BootFailedException() + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. public BootFailedException(string message) : base(message) { } /// - /// Initializes a new instance of the class with a specified error message + /// Initializes a new instance of the class with a specified error message /// and a reference to the inner exception which is the cause of this exception. /// - /// The message that describes the error. - /// The inner exception, or null. - public BootFailedException(string message, Exception inner) - : base(message, inner) + /// The message that describes the error. + /// The inner exception, or null. + public BootFailedException(string message, Exception innerException) + : base(message, innerException) { } /// - /// Rethrows a captured . + /// Initializes a new instance of the class. /// - /// The exception can be null, in which case a default message is used. + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected BootFailedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + + /// + /// Rethrows a captured . + /// + /// The boot failed exception. + /// + /// + /// + /// The exception can be null, in which case a default message is used. + /// public static void Rethrow(BootFailedException bootFailedException) { if (bootFailedException == null) diff --git a/src/Umbraco.Core/Exceptions/ConnectionException.cs b/src/Umbraco.Core/Exceptions/ConnectionException.cs deleted file mode 100644 index 64fdbeee52..0000000000 --- a/src/Umbraco.Core/Exceptions/ConnectionException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Umbraco.Core.Exceptions -{ - internal class ConnectionException : Exception - { - public ConnectionException(string message, Exception innerException) : base(message, innerException) - { - - } - } -} diff --git a/src/Umbraco.Core/Exceptions/DataOperationException.cs b/src/Umbraco.Core/Exceptions/DataOperationException.cs index 14fefcf9d3..c004e391fe 100644 --- a/src/Umbraco.Core/Exceptions/DataOperationException.cs +++ b/src/Umbraco.Core/Exceptions/DataOperationException.cs @@ -1,21 +1,98 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Core.Exceptions { + /// + /// + /// + /// + /// + [Serializable] internal class DataOperationException : Exception + where T : Enum { + /// + /// Gets the operation. + /// + /// + /// The operation. + /// + /// + /// This object should be serializable to prevent a to be thrown. + /// public T Operation { get; private set; } + /// + /// Initializes a new instance of the class. + /// + public DataOperationException() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public DataOperationException(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 DataOperationException(string message, Exception innerException) + : base(message, innerException) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The operation. + public DataOperationException(T operation) + : this(operation, "Data operation exception: " + operation) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The operation. + /// The message. public DataOperationException(T operation, string message) : base(message) { Operation = operation; } - public DataOperationException(T operation) - : base("Data operation exception: " + operation) + /// + /// 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. + /// info + protected DataOperationException(SerializationInfo info, StreamingContext context) + : base(info, context) { - Operation = operation; + Operation = (T)Enum.Parse(typeof(T), info.GetString(nameof(Operation))); + } + + /// + /// 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(Operation), Enum.GetName(typeof(T), Operation)); + + base.GetObjectData(info, context); } } } diff --git a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs index 9d154c6a6f..684e23b020 100644 --- a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs +++ b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs @@ -1,44 +1,126 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Core.Exceptions { + /// + /// The exception that is thrown when a composition is invalid. + /// + /// + [Serializable] public class InvalidCompositionException : Exception { - public InvalidCompositionException(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliass) - { - ContentTypeAlias = contentTypeAlias; - AddedCompositionAlias = addedCompositionAlias; - PropertyTypeAliases = propertyTypeAliass; - } + /// + /// Gets the content type alias. + /// + /// + /// The content type alias. + /// + public string ContentTypeAlias { get; } - public InvalidCompositionException(string contentTypeAlias, string[] propertyTypeAliass) - { - ContentTypeAlias = contentTypeAlias; - PropertyTypeAliases = propertyTypeAliass; - } + /// + /// Gets the added composition alias. + /// + /// + /// The added composition alias. + /// + public string AddedCompositionAlias { get; } - public string ContentTypeAlias { get; private set; } + /// + /// Gets the property type aliases. + /// + /// + /// The property type aliases. + /// + public string[] PropertyTypeAliases { get; } - public string AddedCompositionAlias { get; private set; } + /// + /// Initializes a new instance of the class. + /// + public InvalidCompositionException() + { } - public string[] PropertyTypeAliases { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// The content type alias. + /// The property type aliases. + public InvalidCompositionException(string contentTypeAlias, string[] propertyTypeAliases) + : this(contentTypeAlias, null, propertyTypeAliases) + { } - public override string Message - { - get - { - return AddedCompositionAlias.IsNullOrWhiteSpace() + /// + /// Initializes a new instance of the class. + /// + /// The content type alias. + /// The added composition alias. + /// The property type aliases. + public InvalidCompositionException(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliases) + : this(addedCompositionAlias.IsNullOrWhiteSpace() ? string.Format( "ContentType with alias '{0}' has an invalid composition " + "and there was a conflict on the following PropertyTypes: '{1}'. " + "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.", - ContentTypeAlias, string.Join(", ", PropertyTypeAliases)) + contentTypeAlias, string.Join(", ", propertyTypeAliases)) : string.Format( "ContentType with alias '{0}' was added as a Composition to ContentType with alias '{1}', " + "but there was a conflict on the following PropertyTypes: '{2}'. " + "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.", - AddedCompositionAlias, ContentTypeAlias, string.Join(", ", PropertyTypeAliases)); + addedCompositionAlias, contentTypeAlias, string.Join(", ", propertyTypeAliases))) + { + ContentTypeAlias = contentTypeAlias; + AddedCompositionAlias = addedCompositionAlias; + PropertyTypeAliases = propertyTypeAliases; + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public InvalidCompositionException(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 InvalidCompositionException(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 InvalidCompositionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + ContentTypeAlias = info.GetString(nameof(ContentTypeAlias)); + AddedCompositionAlias = info.GetString(nameof(AddedCompositionAlias)); + PropertyTypeAliases = (string[])info.GetValue(nameof(PropertyTypeAliases), typeof(string[])); + } + + /// + /// 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(ContentTypeAlias), ContentTypeAlias); + info.AddValue(nameof(AddedCompositionAlias), AddedCompositionAlias); + info.AddValue(nameof(PropertyTypeAliases), PropertyTypeAliases); + + base.GetObjectData(info, context); } } } diff --git a/src/Umbraco.Core/Exceptions/PanicException.cs b/src/Umbraco.Core/Exceptions/PanicException.cs index 4d41cafc65..75edf7fd73 100644 --- a/src/Umbraco.Core/Exceptions/PanicException.cs +++ b/src/Umbraco.Core/Exceptions/PanicException.cs @@ -4,25 +4,42 @@ using System.Runtime.Serialization; namespace Umbraco.Core.Exceptions { /// - /// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen + /// Represents an internal exception that in theory should never been thrown, it is only thrown in circumstances that should never happen. /// + /// [Serializable] - internal class PanicException : Exception + public class PanicException : Exception { + /// + /// Initializes a new instance of the class. + /// public PanicException() - { - } + { } - public PanicException(string message) : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public PanicException(string message) + : base(message) + { } - public PanicException(string message, Exception innerException) : base(message, innerException) - { - } + /// + /// 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 PanicException(string message, Exception innerException) + : base(message, innerException) + { } - protected PanicException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + /// + /// 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 PanicException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } } diff --git a/src/Umbraco.Core/Exceptions/WontImplementException.cs b/src/Umbraco.Core/Exceptions/WontImplementException.cs index 7774bf53de..e354bc4c3d 100644 --- a/src/Umbraco.Core/Exceptions/WontImplementException.cs +++ b/src/Umbraco.Core/Exceptions/WontImplementException.cs @@ -1,27 +1,52 @@ 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, + /// + /// 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. + /// 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. + /// Initializes a new instance of the class. /// public WontImplementException() { } /// - /// Initializes a new instance of the class with a specified reason message. + /// 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.Core/IMainDom.cs b/src/Umbraco.Core/IMainDom.cs index 3a8cd13ff1..31b2e2eee0 100644 --- a/src/Umbraco.Core/IMainDom.cs +++ b/src/Umbraco.Core/IMainDom.cs @@ -14,6 +14,9 @@ namespace Umbraco.Core /// /// Gets a value indicating whether the current domain is the main domain. /// + /// + /// When the first call is made to this there will generally be some logic executed to acquire a distributed lock lease. + /// bool IsMainDom { get; } /// @@ -35,4 +38,4 @@ namespace Umbraco.Core /// is guaranteed to execute before the AppDomain releases the main domain status. bool Register(Action install, Action release, int weight = 100); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/IO/FileSecurityException.cs b/src/Umbraco.Core/IO/FileSecurityException.cs index 7b4f7d2625..8ce9ab34a5 100644 --- a/src/Umbraco.Core/IO/FileSecurityException.cs +++ b/src/Umbraco.Core/IO/FileSecurityException.cs @@ -1,20 +1,46 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +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) + { } - 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.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index 2ce1230bcc..05c02171ba 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.IO; using System.Linq; using System.Threading.Tasks; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; -using Umbraco.Core.Media; using Umbraco.Core.Models; namespace Umbraco.Core.IO @@ -92,7 +87,8 @@ namespace Umbraco.Core.IO { if (content == null) throw new ArgumentNullException(nameof(content)); if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); - if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentNullOrEmptyException(nameof(filename)); + if (filename == null) throw new ArgumentNullException(nameof(filename)); + if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(filename)); if (filestream == null) throw new ArgumentNullException(nameof(filestream)); // clear the old file, if any @@ -111,7 +107,8 @@ namespace Umbraco.Core.IO { if (content == null) throw new ArgumentNullException(nameof(content)); if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); - if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentNullOrEmptyException(nameof(sourcepath)); + if (sourcepath == null) throw new ArgumentNullException(nameof(sourcepath)); + if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(sourcepath)); // ensure we have a file to copy if (FileExists(sourcepath) == false) return null; diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index e4edb2b86b..96aaf7ca27 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; using System.Threading; using Umbraco.Core.Logging; @@ -39,9 +38,11 @@ namespace Umbraco.Core.IO public PhysicalFileSystem(string rootPath, string rootUrl) { - if (string.IsNullOrEmpty(rootPath)) throw new ArgumentNullOrEmptyException(nameof(rootPath)); - if (string.IsNullOrEmpty(rootUrl)) throw new ArgumentNullOrEmptyException(nameof(rootUrl)); - if (rootPath.StartsWith("~/")) throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'"); + if (rootPath == null) throw new ArgumentNullException(nameof(rootPath)); + if (string.IsNullOrEmpty(rootPath)) throw new ArgumentException("Value can't be empty.", nameof(rootPath)); + if (rootUrl == null) throw new ArgumentNullException(nameof(rootUrl)); + if (string.IsNullOrEmpty(rootUrl)) throw new ArgumentException("Value can't be empty.", nameof(rootUrl)); + if (rootPath.StartsWith("~/")) throw new ArgumentException("Value can't be a virtual path and start with '~/'.", nameof(rootPath)); // rootPath should be... rooted, as in, it's a root path! if (Path.IsPathRooted(rootPath) == false) @@ -314,7 +315,7 @@ namespace Umbraco.Core.IO // nothing prevents us to reach the file, security-wise, yet it is outside // this filesystem's root - throw - throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root."); + throw new UnauthorizedAccessException("File '" + opath + "' is outside this filesystem's root."); } /// diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 5da1062275..e2049c0190 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -15,25 +15,26 @@ namespace Umbraco.Core /// When an AppDomain starts, it tries to acquire the main domain status. /// When an AppDomain stops (eg the application is restarting) it should release the main domain status. /// - internal class MainDom : IMainDom, IRegisteredObject + internal class MainDom : IMainDom, IRegisteredObject, IDisposable { #region Vars private readonly ILogger _logger; // our own lock for local consistency - private readonly object _locko = new object(); + private object _locko = new object(); // async lock representing the main domain lock - private readonly AsyncLock _asyncLock; - private IDisposable _asyncLocker; + private readonly SystemLock _systemLock; + private IDisposable _systemLocker; // event wait handle used to notify current main domain that it should // release the lock because a new domain wants to be the main domain private readonly EventWaitHandle _signal; + private bool _isInitialized; // indicates whether... - private volatile bool _isMainDom; // we are the main domain + private bool _isMainDom; // we are the main domain private volatile bool _signaled; // we have been signaled // actions to run before releasing the main domain @@ -48,13 +49,13 @@ namespace Umbraco.Core // initializes a new instance of MainDom public MainDom(ILogger logger) { + HostingEnvironment.RegisterObject(this); + _logger = logger; - var appId = string.Empty; // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail - if (HostingEnvironment.ApplicationID != null) - appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty); - + var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; + // combining with the physical path because if running on eg IIS Express, // two sites could have the same appId even though they are different. // @@ -64,11 +65,11 @@ namespace Umbraco.Core // we *cannot* use the process ID here because when an AppPool restarts it is // a new process for the same application path - var appPath = HostingEnvironment.ApplicationPhysicalPath; + var appPath = HostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; var hash = (appId + ":::" + appPath).GenerateHash(); var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK"; - _asyncLock = new AsyncLock(lockName); + _systemLock = new SystemLock(lockName); var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT"; _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); @@ -99,6 +100,12 @@ namespace Umbraco.Core lock (_locko) { if (_signaled) return false; + if (_isMainDom == false) + { + _logger.Warn("Register called when MainDom has not been acquired"); + return false; + } + install?.Invoke(); if (release != null) _callbacks.Add(new KeyValuePair(weight, release)); @@ -118,64 +125,65 @@ namespace Umbraco.Core if (_signaled) return; if (_isMainDom == false) return; // probably not needed _signaled = true; - } - try - { - _logger.Info("Stopping ({SignalSource})", source); - foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) + try { - try + _logger.Info("Stopping ({SignalSource})", source); + foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) { - callback(); // no timeout on callbacks + try + { + callback(); // no timeout on callbacks + } + catch (Exception e) + { + _logger.Error(e, "Error while running callback"); + continue; + } } - catch (Exception e) - { - _logger.Error(e, "Error while running callback, remaining callbacks will not run."); - throw; - } - + _logger.Debug("Stopped ({SignalSource})", source); } - _logger.Debug("Stopped ({SignalSource})", source); - } - finally - { - // in any case... - _isMainDom = false; - _asyncLocker.Dispose(); - _logger.Info("Released ({SignalSource})", source); + finally + { + // in any case... + _isMainDom = false; + _systemLocker?.Dispose(); + _logger.Info("Released ({SignalSource})", source); + } + } } // acquires the main domain - internal bool Acquire() + private bool Acquire() { - lock (_locko) // we don't want the hosting environment to interfere by signaling + // if signaled, too late to acquire, give up + // the handler is not installed so that would be the hosting environment + if (_signaled) { - // if signaled, too late to acquire, give up - // the handler is not installed so that would be the hosting environment - if (_signaled) - { - _logger.Info("Cannot acquire (signaled)."); - return false; - } + _logger.Info("Cannot acquire (signaled)."); + return false; + } - _logger.Info("Acquiring."); + _logger.Info("Acquiring."); - // signal other instances that we want the lock, then wait one the lock, - // which may timeout, and this is accepted - see comments below + // signal other instances that we want the lock, then wait one the lock, + // which may timeout, and this is accepted - see comments below - // signal, then wait for the lock, then make sure the event is - // reset (maybe there was noone listening..) - _signal.Set(); + // signal, then wait for the lock, then make sure the event is + // reset (maybe there was noone listening..) + _signal.Set(); - // if more than 1 instance reach that point, one will get the lock - // and the other one will timeout, which is accepted - - //TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset? - _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds); - _isMainDom = true; + // if more than 1 instance reach that point, one will get the lock + // and the other one will timeout, which is accepted + //This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. + try + { + _systemLocker = _systemLock.Lock(LockTimeoutMilliseconds); + } + finally + { // we need to reset the event, because otherwise we would end up // signaling ourselves and committing suicide immediately. // only 1 instance can reach that point, but other instances may @@ -183,35 +191,58 @@ namespace Umbraco.Core // which is accepted _signal.Reset(); - - //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread - - _signal.WaitOneAsync() - .ContinueWith(_ => OnSignal("signal")); - - HostingEnvironment.RegisterObject(this); - - _logger.Info("Acquired."); - return true; } + + //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread + + _signal.WaitOneAsync() + .ContinueWith(_ => OnSignal("signal")); + + _logger.Info("Acquired."); + return true; } /// /// Gets a value indicating whether the current domain is the main domain. /// - public bool IsMainDom => _isMainDom; + public bool IsMainDom => LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => Acquire()); // IRegisteredObject void IRegisteredObject.Stop(bool immediate) { - try + OnSignal("environment"); // will run once + + // The web app is stopping, need to wind down + Dispose(true); + + HostingEnvironment.UnregisterObject(this); + } + + #region IDisposable Support + + // This code added to correctly implement the disposable pattern. + + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) { - OnSignal("environment"); // will run once - } - finally - { - HostingEnvironment.UnregisterObject(this); + if (disposing) + { + _signal?.Close(); + _signal?.Dispose(); + } + + disposedValue = true; } } + + public void Dispose() + { + Dispose(true); + } + + #endregion } } diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index efd9e92b1f..1ecc738b95 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text; using Newtonsoft.Json; using Umbraco.Core.Cache; -using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; @@ -42,7 +41,8 @@ namespace Umbraco.Core.Manifest _cache = appCaches.RuntimeCache; _validators = validators ?? throw new ArgumentNullException(nameof(validators)); _filters = filters ?? throw new ArgumentNullException(nameof(filters)); - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); + if (path == null) throw new ArgumentNullException(nameof(path)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(path)); Path = path; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -155,8 +155,8 @@ namespace Umbraco.Core.Manifest /// internal PackageManifest ParseManifest(string text) { - if (string.IsNullOrWhiteSpace(text)) - throw new ArgumentNullOrEmptyException(nameof(text)); + if (text == null) throw new ArgumentNullException(nameof(text)); + if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text)); var manifest = JsonConvert.DeserializeObject(text, new DataEditorConverter(_logger), diff --git a/src/Umbraco.Core/Migrations/DataLossException.cs b/src/Umbraco.Core/Migrations/DataLossException.cs deleted file mode 100644 index 6ff332f626..0000000000 --- a/src/Umbraco.Core/Migrations/DataLossException.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Umbraco.Core.Migrations -{ - /// - /// Used if a migration has executed but the whole process has failed and cannot be rolled back - /// - internal class DataLossException : Exception - { - public DataLossException(string msg) - : base(msg) - { - - } - - public DataLossException(string msg, Exception inner) - : base(msg, inner) - { - - } - } -} diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs index 9a4f437f62..65c15456a5 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Core.Exceptions; +using System; using Umbraco.Core.Migrations.Expressions.Common; using Umbraco.Core.Migrations.Expressions.Delete.Column; using Umbraco.Core.Migrations.Expressions.Delete.Constraint; @@ -39,8 +39,9 @@ namespace Umbraco.Core.Migrations.Expressions.Delete /// public IExecutableBuilder KeysAndIndexes(string tableName, bool local = true, bool foreign = true) { - if (tableName.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(tableName)); + if (tableName == null) throw new ArgumentNullException(nameof(tableName)); + if (string.IsNullOrWhiteSpace(tableName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(tableName)); + return new DeleteKeysAndIndexesBuilder(_context) { TableName = tableName, DeleteLocal = local, DeleteForeign = foreign }; } diff --git a/src/Umbraco.Core/Migrations/IncompleteMigrationExpressionException.cs b/src/Umbraco.Core/Migrations/IncompleteMigrationExpressionException.cs index 91d1838d6f..3c81e2f0e2 100644 --- a/src/Umbraco.Core/Migrations/IncompleteMigrationExpressionException.cs +++ b/src/Umbraco.Core/Migrations/IncompleteMigrationExpressionException.cs @@ -1,28 +1,49 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Core.Migrations { /// - /// Represents errors that occurs when a migration exception is not executed. + /// The exception that is thrown when a migration expression is not executed. /// /// - /// Migration expression such as Alter.Table(...).Do() *must* end with Do() else they are - /// not executed. When a non-executed expression is detected, an IncompleteMigrationExpressionException - /// is thrown. + /// Migration expressions such as Alter.Table(...).Do() must end with Do(), else they are not executed. + /// When a non-executed expression is detected, an IncompleteMigrationExpressionException is thrown. /// + /// + [Serializable] public class IncompleteMigrationExpressionException : Exception { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public IncompleteMigrationExpressionException() { } /// - /// Initializes a new instance of the class with a message. + /// Initializes a new instance of the class with a message. /// + /// The message that describes the error. public IncompleteMigrationExpressionException(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 IncompleteMigrationExpressionException(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 IncompleteMigrationExpressionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index d86c682bd5..d5d8bbab6f 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Xml.Linq; using Umbraco.Core.Configuration; -using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Upgrade; @@ -278,8 +277,10 @@ namespace Umbraco.Core.Migrations.Install /// A logger. private static void SaveConnectionString(string connectionString, string providerName, ILogger logger) { - if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullOrEmptyException(nameof(connectionString)); - if (string.IsNullOrWhiteSpace(providerName)) throw new ArgumentNullOrEmptyException(nameof(providerName)); + if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); + if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(connectionString)); + if (providerName == null) throw new ArgumentNullException(nameof(providerName)); + if (string.IsNullOrWhiteSpace(providerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(providerName)); var fileSource = "web.config"; var fileName = IOHelper.MapPath(SystemDirectories.Root +"/" + fileSource); diff --git a/src/Umbraco.Core/Migrations/MigrationPlan.cs b/src/Umbraco.Core/Migrations/MigrationPlan.cs index 37d1a03a5a..89c3c809e8 100644 --- a/src/Umbraco.Core/Migrations/MigrationPlan.cs +++ b/src/Umbraco.Core/Migrations/MigrationPlan.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; using Type = System.Type; @@ -25,7 +24,9 @@ namespace Umbraco.Core.Migrations /// The name of the plan. public MigrationPlan(string name) { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + Name = name; } @@ -43,7 +44,8 @@ namespace Umbraco.Core.Migrations private MigrationPlan Add(string sourceState, string targetState, Type migration) { if (sourceState == null) throw new ArgumentNullException(nameof(sourceState)); - if (string.IsNullOrWhiteSpace(targetState)) throw new ArgumentNullOrEmptyException(nameof(targetState)); + if (targetState == null) throw new ArgumentNullException(nameof(targetState)); + if (string.IsNullOrWhiteSpace(targetState)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(targetState)); if (sourceState == targetState) throw new ArgumentException("Source and target state cannot be identical."); if (migration == null) throw new ArgumentNullException(nameof(migration)); if (!migration.Implements()) throw new ArgumentException($"Type {migration.Name} does not implement IMigration.", nameof(migration)); @@ -135,10 +137,12 @@ namespace Umbraco.Core.Migrations /// public MigrationPlan ToWithClone(string startState, string endState, string targetState) { - if (string.IsNullOrWhiteSpace(startState)) throw new ArgumentNullOrEmptyException(nameof(startState)); - if (string.IsNullOrWhiteSpace(endState)) throw new ArgumentNullOrEmptyException(nameof(endState)); - if (string.IsNullOrWhiteSpace(targetState)) throw new ArgumentNullOrEmptyException(nameof(targetState)); - + if (startState == null) throw new ArgumentNullException(nameof(startState)); + if (string.IsNullOrWhiteSpace(startState)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(startState)); + if (endState == null) throw new ArgumentNullException(nameof(endState)); + if (string.IsNullOrWhiteSpace(endState)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(endState)); + if (targetState == null) throw new ArgumentNullException(nameof(targetState)); + if (string.IsNullOrWhiteSpace(targetState)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(targetState)); if (startState == endState) throw new ArgumentException("Start and end states cannot be identical."); startState = startState.Trim(); @@ -317,7 +321,7 @@ namespace Umbraco.Core.Migrations // throw a raw exception here: this should never happen as the plan has // been validated - this is just a paranoid safety test if (!_transitions.TryGetValue(origState, out transition)) - throw new Exception($"Unknown state \"{origState}\"."); + throw new InvalidOperationException($"Unknown state \"{origState}\"."); } // prepare and de-duplicate post-migrations, only keeping the 1st occurence @@ -339,7 +343,7 @@ namespace Umbraco.Core.Migrations // safety check - again, this should never happen as the plan has been validated, // and this is just a paranoid safety test if (origState != _finalState) - throw new Exception($"Internal error, reached state {origState} which is not final state {_finalState}"); + throw new InvalidOperationException($"Internal error, reached state {origState} which is not final state {_finalState}"); return origState; } @@ -358,7 +362,7 @@ namespace Umbraco.Core.Migrations var states = new List { origState }; if (!_transitions.TryGetValue(origState, out var transition)) - throw new Exception($"Unknown state \"{origState}\"."); + throw new InvalidOperationException($"Unknown state \"{origState}\"."); while (transition != null) { @@ -373,12 +377,12 @@ namespace Umbraco.Core.Migrations } if (!_transitions.TryGetValue(origState, out transition)) - throw new Exception($"Unknown state \"{origState}\"."); + throw new InvalidOperationException($"Unknown state \"{origState}\"."); } // safety check if (origState != (toState ?? _finalState)) - throw new Exception($"Internal error, reached state {origState} which is not state {toState ?? _finalState}"); + throw new InvalidOperationException($"Internal error, reached state {origState} which is not state {toState ?? _finalState}"); return states; } @@ -417,7 +421,7 @@ namespace Umbraco.Core.Migrations public override string ToString() { return MigrationType == typeof(NoopMigration) - ? $"{(SourceState == "" ? "" : SourceState)} --> {TargetState}" + ? $"{(SourceState == string.Empty ? "" : SourceState)} --> {TargetState}" : $"{SourceState} -- ({MigrationType.FullName}) --> {TargetState}"; } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 77dcaa808e..ab3c40d917 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -183,9 +183,11 @@ namespace Umbraco.Core.Migrations.Upgrade To("{0372A42B-DECF-498D-B4D1-6379E907EB94}"); To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}"); - // to 8.5.0... + // to 8.6.0... To("{4759A294-9860-46BC-99F9-B4C975CAE580}"); To("{0BC866BC-0665-487A-9913-0290BD0169AD}"); + To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}"); + //FINAL } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs new file mode 100644 index 0000000000..30eb30109e --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddPropertyTypeValidationMessageColumns.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + public class AddPropertyTypeValidationMessageColumns : MigrationBase + { + public AddPropertyTypeValidationMessageColumns(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + AddColumnIfNotExists(columns, "mandatoryMessage"); + AddColumnIfNotExists(columns, "validationRegExpMessage"); + } + } +} diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index fbb68194b7..d02ea82012 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -4,8 +4,6 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; -using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models @@ -229,8 +227,8 @@ namespace Umbraco.Core.Models private void ClearCultureInfo(string culture) { - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); + if (culture == null) throw new ArgumentNullException(nameof(culture)); + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); if (_cultureInfos == null) return; _cultureInfos.Remove(culture); diff --git a/src/Umbraco.Core/Models/ContentCultureInfos.cs b/src/Umbraco.Core/Models/ContentCultureInfos.cs index c8c4bea56a..2f9c08b985 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfos.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfos.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models @@ -18,7 +17,9 @@ namespace Umbraco.Core.Models /// public ContentCultureInfos(string culture) { - if (culture.IsNullOrWhiteSpace()) throw new ArgumentNullOrEmptyException(nameof(culture)); + if (culture == null) throw new ArgumentNullException(nameof(culture)); + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); + Culture = culture; } diff --git a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs index 52f6f9adb6..9bc78fc56d 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Collections.Specialized; using Umbraco.Core.Collections; -using Umbraco.Core.Exceptions; namespace Umbraco.Core.Models { @@ -23,7 +21,9 @@ namespace Umbraco.Core.Models /// public void AddOrUpdate(string culture, string name, DateTime date) { - if (culture.IsNullOrWhiteSpace()) throw new ArgumentNullOrEmptyException(nameof(culture)); + if (culture == null) throw new ArgumentNullException(nameof(culture)); + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); + culture = culture.ToLowerInvariant(); if (TryGetValue(culture, out var item)) diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs index f9efc60142..af8c781072 100644 --- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Exceptions; namespace Umbraco.Core.Models { @@ -103,11 +102,11 @@ namespace Umbraco.Core.Models public static void SetPublishInfo(this IContent content, string culture, string name, DateTime date) { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullOrEmptyException(nameof(name)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); + if (culture == null) throw new ArgumentNullException(nameof(culture)); + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); content.PublishCultureInfos.AddOrUpdate(culture, name, date); } @@ -153,11 +152,11 @@ namespace Umbraco.Core.Models public static void SetCultureInfo(this IContentBase content, string culture, string name, DateTime date) { - if (name.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(name)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); + if (culture == null) throw new ArgumentNullException(nameof(culture)); + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); content.CultureInfos.AddOrUpdate(culture, name, date); } @@ -276,8 +275,8 @@ namespace Umbraco.Core.Models /// public static bool ClearPublishInfo(this IContent content, string culture) { - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); + if (culture == null) throw new ArgumentNullException(nameof(culture)); + if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); var removed = content.PublishCultureInfos.Remove(culture); if (removed) diff --git a/src/Umbraco.Core/Models/Entities/EntitySlim.cs b/src/Umbraco.Core/Models/Entities/EntitySlim.cs index b095965056..b4b09b58c2 100644 --- a/src/Umbraco.Core/Models/Entities/EntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/EntitySlim.cs @@ -66,7 +66,7 @@ namespace Umbraco.Core.Models.Entities public int ParentId { get; set; } /// - public void SetParent(ITreeEntity parent) => throw new WontImplementException(); + public void SetParent(ITreeEntity parent) => throw new InvalidOperationException("This property won't be implemented."); /// [DataMember] @@ -116,7 +116,7 @@ namespace Umbraco.Core.Models.Entities /// public object DeepClone() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } #endregion @@ -128,47 +128,47 @@ namespace Umbraco.Core.Models.Entities public bool IsDirty() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } public bool IsPropertyDirty(string propName) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } public IEnumerable GetDirtyProperties() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } public void ResetDirtyProperties() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } public bool WasDirty() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } public bool WasPropertyDirty(string propertyName) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } public void ResetWereDirtyProperties() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } public void ResetDirtyProperties(bool rememberDirty) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } public IEnumerable GetWereDirtyProperties() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } #endregion diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs index 1166698adb..96f183b6e6 100644 --- a/src/Umbraco.Core/Models/MediaExtensions.cs +++ b/src/Umbraco.Core/Models/MediaExtensions.cs @@ -1,10 +1,8 @@ -using System; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Linq; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models { @@ -18,29 +16,12 @@ namespace Umbraco.Core.Models if (!media.Properties.TryGetValue(propertyAlias, out var property)) return string.Empty; - // TODO: would need to be adjusted to variations, when media become variants - if (!(property.GetValue() is string jsonString)) - return string.Empty; - - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField) - return jsonString; - - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper) + if (Current.PropertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) { - if (jsonString.DetectIsJson() == false) - return jsonString; - - try - { - var json = JsonConvert.DeserializeObject(jsonString); - if (json["src"] != null) - return json["src"].Value(); - } - catch (Exception ex) - { - logger.Error(ex, "Could not parse the string '{JsonString}' to a json object", jsonString); - return string.Empty; - } + // TODO: would need to be adjusted to variations, when media become variants + var value = property.GetValue(); + return dataEditor.GetMediaPath(value); } // Without knowing what it is, just adding a string here might not be very nice diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index b473a154f1..49e07a486d 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; namespace Umbraco.Core.Models @@ -43,7 +42,8 @@ namespace Umbraco.Core.Models public Member(string name, IMemberType contentType) : base(name, -1, contentType, new PropertyCollection()) { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); IsApproved = true; @@ -63,9 +63,12 @@ namespace Umbraco.Core.Models public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true) : base(name, -1, contentType, new PropertyCollection()) { - if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullOrEmptyException(nameof(email)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullOrEmptyException(nameof(username)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + if (email == null) throw new ArgumentNullException(nameof(email)); + if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(email)); + if (username == null) throw new ArgumentNullException(nameof(username)); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); _email = email; _username = username; diff --git a/src/Umbraco.Core/Models/PagedResult.cs b/src/Umbraco.Core/Models/PagedResult.cs index ef4d4efdfd..4119751eb3 100644 --- a/src/Umbraco.Core/Models/PagedResult.cs +++ b/src/Umbraco.Core/Models/PagedResult.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Runtime.Serialization; namespace Umbraco.Core.Models @@ -9,7 +8,7 @@ namespace Umbraco.Core.Models /// /// [DataContract(Name = "pagedCollection", Namespace = "")] - public class PagedResult + public abstract class PagedResult { public PagedResult(long totalItems, long pageNumber, long pageSize) { @@ -39,9 +38,6 @@ namespace Umbraco.Core.Models [DataMember(Name = "totalItems")] public long TotalItems { get; private set; } - [DataMember(Name = "items")] - public IEnumerable Items { get; set; } - /// /// Calculates the skip size based on the paged parameters specified /// diff --git a/src/Umbraco.Core/Models/PagedResultOfT.cs b/src/Umbraco.Core/Models/PagedResultOfT.cs new file mode 100644 index 0000000000..efb68863dd --- /dev/null +++ b/src/Umbraco.Core/Models/PagedResultOfT.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a paged result for a model collection + /// + /// + [DataContract(Name = "pagedCollection", Namespace = "")] + public class PagedResult : PagedResult + { + public PagedResult(long totalItems, long pageNumber, long pageSize) + : base(totalItems, pageNumber, pageSize) + { } + + [DataMember(Name = "items")] + public IEnumerable Items { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 61736607c6..fd23756acb 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -26,8 +26,10 @@ namespace Umbraco.Core.Models private string _propertyEditorAlias; private ValueStorageType _valueStorageType; private bool _mandatory; + private string _mandatoryMessage; private int _sortOrder; private string _validationRegExp; + private string _validationRegExpMessage; private ContentVariation _variations; /// @@ -183,7 +185,7 @@ namespace Umbraco.Core.Models } /// - /// Gets of sets a value indicating whether a value for this property type is required. + /// Gets or sets a value indicating whether a value for this property type is required. /// [DataMember] public bool Mandatory @@ -192,6 +194,16 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(Mandatory)); } + /// + /// Gets or sets the custom validation message used when a value for this PropertyType is required + /// + [DataMember] + public string MandatoryMessage + { + get => _mandatoryMessage; + set => SetPropertyValueAndDetectChanges(value, ref _mandatoryMessage, nameof(MandatoryMessage)); + } + /// /// Gets of sets the sort order of the property type. /// @@ -212,6 +224,16 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, nameof(ValidationRegExp)); } + /// + /// Gets or sets the custom validation message used when a pattern for this PropertyType must be matched + /// + [DataMember] + public string ValidationRegExpMessage + { + get => _validationRegExpMessage; + set => SetPropertyValueAndDetectChanges(value, ref _validationRegExpMessage, nameof(ValidationRegExpMessage)); + } + /// /// Gets or sets the content variation of the property type. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs index 318ccc916e..dd60eb9beb 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs @@ -20,7 +20,9 @@ namespace Umbraco.Core.Models.PublishedContent { private ModelType(string contentTypeAlias) { - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(contentTypeAlias)); + if (contentTypeAlias == null) throw new ArgumentNullException(nameof(contentTypeAlias)); + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); + ContentTypeAlias = contentTypeAlias; Name = "{" + ContentTypeAlias + "}"; } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index d5096158a7..908b97fc36 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -1,5 +1,4 @@ using System; -using Umbraco.Core.Exceptions; namespace Umbraco.Core.Models.PublishedContent { @@ -13,7 +12,8 @@ namespace Umbraco.Core.Models.PublishedContent /// public PublishedCultureInfo(string culture, string name, string urlSegment, DateTime date) { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); Culture = culture ?? throw new ArgumentNullException(nameof(culture)); Name = name; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs index 16eb614ee7..3f2c63a78f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs @@ -1,5 +1,4 @@ using System; -using Umbraco.Core.Exceptions; namespace Umbraco.Core.Models.PublishedContent { @@ -19,7 +18,9 @@ namespace Umbraco.Core.Models.PublishedContent /// The content type alias. public PublishedModelAttribute(string contentTypeAlias) { - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(contentTypeAlias)); + if (contentTypeAlias == null) throw new ArgumentNullException(nameof(contentTypeAlias)); + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); + ContentTypeAlias = contentTypeAlias; } diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index ea62cecab7..1085ecdcdd 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.Serialization; -using Umbraco.Core.Exceptions; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs index 281cc2c396..6a5acb0dc7 100644 --- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs @@ -787,7 +787,14 @@ namespace Umbraco.Core.Packaging Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false, + MandatoryMessage = property.Element("MandatoryMessage") != null + ? (string)property.Element("MandatoryMessage") + : string.Empty, + ValidationRegExp = (string)property.Element("Validation"), + ValidationRegExpMessage = property.Element("ValidationRegExpMessage") != null + ? (string)property.Element("ValidationRegExpMessage") + : string.Empty, SortOrder = sortOrder, Variations = property.Element("Variations") != null ? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value) diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs index 8c52aa1e15..3e8d6e7496 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs @@ -43,10 +43,20 @@ namespace Umbraco.Core.Persistence.Dtos [Constraint(Default = "0")] public bool Mandatory { get; set; } + [Column("mandatoryMessage")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] + public string MandatoryMessage { get; set; } + [Column("validationRegExp")] [NullSetting(NullSetting = NullSettings.Null)] public string ValidationRegExp { get; set; } + [Column("validationRegExpMessage")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] + public string ValidationRegExpMessage { get; set; } + [Column("Description")] [NullSetting(NullSetting = NullSettings.Null)] [Length(2000)] diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs index c68dee42b5..4c352a0134 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs @@ -32,9 +32,15 @@ namespace Umbraco.Core.Persistence.Dtos [Column("mandatory")] public bool Mandatory { get; set; } + [Column("mandatoryMessage")] + public string MandatoryMessage { get; set; } + [Column("validationRegExp")] public string ValidationRegExp { get; set; } + [Column("validationRegExpMessage")] + public string ValidationRegExpMessage { get; set; } + [Column("Description")] public string Description { get; set; } diff --git a/src/Umbraco.Core/Persistence/EntityNotFoundException.cs b/src/Umbraco.Core/Persistence/EntityNotFoundException.cs index e0fe778fa6..ea6d5142f0 100644 --- a/src/Umbraco.Core/Persistence/EntityNotFoundException.cs +++ b/src/Umbraco.Core/Persistence/EntityNotFoundException.cs @@ -1,38 +1,96 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.Serialization; namespace Umbraco.Core.Persistence { - - // TODO: Would be good to use this exception type anytime we cannot find an entity - /// - /// An exception used to indicate that an umbraco entity could not be found + /// 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; } - private readonly string _msg; - public EntityNotFoundException(object id, string msg) + /// + /// 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; - _msg = msg; } - public EntityNotFoundException(string msg) + /// + /// 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) { - _msg = msg; + Id = info.GetValue(nameof(Id), typeof(object)); } - public override string Message + /// + /// 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) { - get { return _msg; } + 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(); diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 434e0393cd..2b2bed1d9e 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Persistence.Factories { internal class ContentBaseFactory { - private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled); - /// /// Builds an IContent item from a dto and content type. /// @@ -189,7 +187,7 @@ namespace Umbraco.Core.Persistence.Factories /// /// Builds a dto from an IMedia item. /// - public static MediaDto BuildDto(IMedia entity) + public static MediaDto BuildDto(PropertyEditorCollection propertyEditors, IMedia entity) { var contentDto = BuildContentDto(entity, Constants.ObjectTypes.Media); @@ -197,7 +195,7 @@ namespace Umbraco.Core.Persistence.Factories { NodeId = entity.Id, ContentDto = contentDto, - MediaVersionDto = BuildMediaVersionDto(entity, contentDto) + MediaVersionDto = BuildMediaVersionDto(propertyEditors, entity, contentDto) }; return dto; @@ -291,12 +289,20 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - private static MediaVersionDto BuildMediaVersionDto(IMedia entity, ContentDto contentDto) + private static MediaVersionDto BuildMediaVersionDto(PropertyEditorCollection propertyEditors, IMedia entity, ContentDto contentDto) { // try to get a path from the string being stored for media // TODO: only considering umbracoFile - TryMatch(entity.GetValue("umbracoFile"), out var path); + string path = null; + + if (entity.Properties.TryGetValue(Constants.Conventions.Media.File, out var property) + && propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) + { + var value = property.GetValue(); + path = dataEditor.GetMediaPath(value); + } var dto = new MediaVersionDto { @@ -308,22 +314,5 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - - // TODO: this should NOT be here?! - // more dark magic ;-( - internal static bool TryMatch(string text, out string path) - { - // In v8 we should allow exposing this via the property editor in a much nicer way so that the property editor - // can tell us directly what any URL is for a given property if it contains an asset - - path = null; - if (string.IsNullOrWhiteSpace(text)) return false; - - var m = MediaPathPattern.Match(text); - if (!m.Success || m.Groups.Count != 2) return false; - - path = m.Groups[1].Value; - return true; - } } } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index db8e2b20d9..dc1629e8f7 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -60,8 +60,10 @@ namespace Umbraco.Core.Persistence.Factories propertyType.Key = typeDto.UniqueId; propertyType.Name = typeDto.Name; propertyType.Mandatory = typeDto.Mandatory; + propertyType.MandatoryMessage = typeDto.MandatoryMessage; propertyType.SortOrder = typeDto.SortOrder; propertyType.ValidationRegExp = typeDto.ValidationRegExp; + propertyType.ValidationRegExpMessage = typeDto.ValidationRegExpMessage; propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); propertyType.CreateDate = createDate; propertyType.UpdateDate = updateDate; @@ -124,9 +126,11 @@ namespace Umbraco.Core.Persistence.Factories DataTypeId = propertyType.DataTypeId, Description = propertyType.Description, Mandatory = propertyType.Mandatory, + MandatoryMessage = propertyType.MandatoryMessage, Name = propertyType.Name, SortOrder = propertyType.SortOrder, ValidationRegExp = propertyType.ValidationRegExp, + ValidationRegExpMessage = propertyType.ValidationRegExpMessage, UniqueId = propertyType.Key, Variations = (byte)propertyType.Variations }; diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs index c537281dc9..a1a0db2983 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryLimitExceededException.cs @@ -4,59 +4,51 @@ using System.Runtime.Serialization; namespace Umbraco.Core.Persistence.FaultHandling { /// - /// The special type of exception that provides managed exit from a retry loop. The user code can use this - /// exception to notify the retry policy that no further retry attempts are required. + /// The special type of exception that provides managed exit from a retry loop. The user code can use this exception to notify the retry policy that no further retry attempts are required. /// + /// [Serializable] public sealed class RetryLimitExceededException : Exception { /// - /// Initializes a new instance of the class with a default error message. + /// Initializes a new instance of the class with a default error message. /// public RetryLimitExceededException() - : this("RetryLimitExceeded") - { - } + : base() + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. public RetryLimitExceededException(string message) : base(message) - { - } + { } /// - /// Initializes a new instance of the class with a reference to the inner exception - /// that is the cause of this exception. + /// Initializes a new instance of the class with a reference to the inner exception that is the cause of this exception. /// /// The exception that is the cause of the current exception. public RetryLimitExceededException(Exception innerException) - : base(innerException != null ? innerException.Message : "RetryLimitExceeded", innerException) - { - } + : base(null, innerException) + { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The message that describes the error. /// The exception that is the cause of the current exception. public RetryLimitExceededException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// - /// Initializes a new instance of the class. + /// 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. - /// The parameter is null. - /// The class name is null or is zero (0). + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. private RetryLimitExceededException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } } } diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index ab1869a7f5..6f22b61f9a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -24,9 +24,11 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(PropertyType.DataTypeId), nameof(PropertyTypeDto.DataTypeId)); DefineMap(nameof(PropertyType.Description), nameof(PropertyTypeDto.Description)); DefineMap(nameof(PropertyType.Mandatory), nameof(PropertyTypeDto.Mandatory)); + DefineMap(nameof(PropertyType.MandatoryMessage), nameof(PropertyTypeDto.MandatoryMessage)); DefineMap(nameof(PropertyType.Name), nameof(PropertyTypeDto.Name)); DefineMap(nameof(PropertyType.SortOrder), nameof(PropertyTypeDto.SortOrder)); DefineMap(nameof(PropertyType.ValidationRegExp), nameof(PropertyTypeDto.ValidationRegExp)); + DefineMap(nameof(PropertyType.ValidationRegExpMessage), nameof(PropertyTypeDto.ValidationRegExpMessage)); DefineMap(nameof(PropertyType.PropertyEditorAlias), nameof(DataTypeDto.EditorAlias)); DefineMap(nameof(PropertyType.ValueStorageType), nameof(DataTypeDto.DbType)); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 4393d365f8..7781e2e38a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -297,10 +297,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Id = dto.Id, Key = dto.UniqueId, Mandatory = dto.Mandatory, + MandatoryMessage = dto.MandatoryMessage, Name = dto.Name, PropertyGroupId = groupId.HasValue ? new Lazy(() => groupId.Value) : null, SortOrder = dto.SortOrder, ValidationRegExp = dto.ValidationRegExp, + ValidationRegExpMessage = dto.ValidationRegExpMessage, Variations = (ContentVariation)dto.Variations }; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 35e54a39e0..98e51995f8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -944,32 +944,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable GetDeleteClauses() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override void PersistNewItem(IContent entity) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override void PersistUpdatedItem(IContent entity) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override Sql GetBaseQuery(bool isCount) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override string GetBaseWhereClause() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs index 09fe949df1..505cbfc816 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -129,6 +128,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistDeletedItem(EntityContainer entity) { + if (entity == null) throw new ArgumentNullException(nameof(entity)); EnsureContainerType(entity); var nodeDto = Database.FirstOrDefault(Sql().SelectAll() @@ -162,9 +162,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(EntityContainer entity) { + if (entity == null) throw new ArgumentNullException(nameof(entity)); EnsureContainerType(entity); - if (string.IsNullOrWhiteSpace(entity.Name)) throw new ArgumentNullOrEmptyException("entity.Name"); + if (entity.Name == null) throw new InvalidOperationException("Entity name can't be null."); + if (string.IsNullOrWhiteSpace(entity.Name)) throw new InvalidOperationException("Entity name can't be empty or consist only of white-space characters."); entity.Name = entity.Name.Trim(); // guard against duplicates @@ -184,7 +186,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); if (parentDto == null) - throw new NullReferenceException("Could not find parent container with id " + entity.ParentId); + throw new InvalidOperationException("Could not find parent container with id " + entity.ParentId); level = parentDto.Level; path = parentDto.Path; @@ -223,10 +225,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // protected override void PersistUpdatedItem(EntityContainer entity) { + if (entity == null) throw new ArgumentNullException(nameof(entity)); EnsureContainerType(entity); + if (entity.Name == null) throw new InvalidOperationException("Entity name can't be null."); + if (string.IsNullOrWhiteSpace(entity.Name)) throw new InvalidOperationException("Entity name can't be empty or consist only of white-space characters."); entity.Name = entity.Name.Trim(); - if (string.IsNullOrWhiteSpace(entity.Name)) throw new ArgumentNullOrEmptyException("entity.Name"); // find container to update var nodeDto = Database.FirstOrDefault(Sql().SelectAll() @@ -255,7 +259,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); if (parent == null) - throw new NullReferenceException("Could not find parent container with id " + entity.ParentId); + throw new InvalidOperationException("Could not find parent container with id " + entity.ParentId); nodeDto.Level = Convert.ToInt16(parent.Level + 1); nodeDto.Path = parent.Path + "," + nodeDto.NodeId; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index ad47c7af6b..081efcfdf6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -230,7 +230,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.SanitizeEntityPropertiesForXmlStorage(); // create the dto - var dto = ContentBaseFactory.BuildDto(entity); + var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity); // derive path and level from parent var parent = GetParentNodeDto(entity.ParentId); @@ -321,7 +321,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // create the dto - var dto = ContentBaseFactory.BuildDto(entity); + var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity); // update the node dto var nodeDto = dto.ContentDto.NodeDto; @@ -431,32 +431,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable GetDeleteClauses() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override void PersistNewItem(IMedia entity) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override void PersistUpdatedItem(IMedia entity) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override Sql GetBaseQuery(bool isCount) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override string GetBaseWhereClause() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } } @@ -548,6 +548,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement media.ResetDirtyProperties(false); return media; } - + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index ee651819bf..c3b95dbd8f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -225,8 +225,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (builtinProperties.ContainsKey(propertyType.Alias)) { //this reset's its current data type reference which will be re-assigned based on the property editor assigned on the next line - propertyType.DataTypeId = 0; - propertyType.DataTypeKey = default; + var propDefinition = builtinProperties[propertyType.Alias]; + if (propDefinition != null) + { + propertyType.DataTypeId = propDefinition.DataTypeId; + propertyType.DataTypeKey = propDefinition.DataTypeKey; + } + else + { + propertyType.DataTypeId = 0; + propertyType.DataTypeKey = default; + } } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs index b4fd86c567..259f0b89c0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PermissionRepository.cs @@ -252,27 +252,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override ContentPermissionSet PerformGet(int id) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable PerformGetAll(params int[] ids) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override Sql GetBaseQuery(bool isCount) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override string GetBaseWhereClause() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable GetDeleteClauses() @@ -280,11 +280,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return new List(); } - protected override Guid NodeObjectTypeId => throw new WontImplementException(); + protected override Guid NodeObjectTypeId => throw new InvalidOperationException("This property won't be implemented."); protected override void PersistDeletedItem(ContentPermissionSet entity) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } #endregion diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs index 43233d0f31..f7e59820c3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs @@ -75,19 +75,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected sealed override IEnumerable GetDeleteClauses() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } - protected sealed override Guid NodeObjectTypeId => throw new WontImplementException(); + protected sealed override Guid NodeObjectTypeId => throw new InvalidOperationException("This property won't be implemented."); protected sealed override void PersistNewItem(TEntity entity) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected sealed override void PersistUpdatedItem(TEntity entity) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } #endregion diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs index 0701a0996e..0a66e29147 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -286,7 +286,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return list; } - protected override Guid NodeObjectTypeId => throw new WontImplementException(); + protected override Guid NodeObjectTypeId => throw new InvalidOperationException("This property won't be implemented."); protected override void PersistNewItem(IUserGroup entity) { @@ -370,35 +370,35 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override UserGroupWithUsers PerformGet(int id) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable PerformGetAll(params int[] ids) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable PerformGetByQuery(IQuery query) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override Sql GetBaseQuery(bool isCount) { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override string GetBaseWhereClause() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } protected override IEnumerable GetDeleteClauses() { - throw new WontImplementException(); + throw new InvalidOperationException("This method won't be implemented."); } - protected override Guid NodeObjectTypeId => throw new WontImplementException(); + protected override Guid NodeObjectTypeId => throw new InvalidOperationException("This property won't be implemented."); #endregion diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index 13422f43b1..c502abc87c 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -4,7 +4,6 @@ using System.Data.Common; using System.Threading; using NPoco; using NPoco.FluentMappings; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.FaultHandling; using Umbraco.Core.Persistence.Mappers; @@ -61,8 +60,8 @@ namespace Umbraco.Core.Persistence /// Used by the other ctor and in tests. public UmbracoDatabaseFactory(string connectionStringName, ILogger logger, Lazy mappers) { - if (string.IsNullOrWhiteSpace(connectionStringName)) - throw new ArgumentNullOrEmptyException(nameof(connectionStringName)); + if (connectionStringName == null) throw new ArgumentNullException(nameof(connectionStringName)); + if (string.IsNullOrWhiteSpace(connectionStringName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(connectionStringName)); _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs index d90aeef2e6..1852c742f0 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs @@ -1,5 +1,4 @@ using System; -using Umbraco.Core.Exceptions; namespace Umbraco.Core.PropertyEditors { @@ -28,13 +27,15 @@ namespace Umbraco.Core.PropertyEditors /// The view to use to render the field editor. public ConfigurationFieldAttribute(string key, string name, string view) { - if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullOrEmptyException(nameof(key)); + if (key == null) throw new ArgumentNullException(nameof(key)); + if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(key)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + if (view == null) throw new ArgumentNullException(nameof(view)); + if (string.IsNullOrWhiteSpace(view)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(view)); + Key = key; - - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); Name = name; - - if (string.IsNullOrWhiteSpace(view)) throw new ArgumentNullOrEmptyException(nameof(view)); View = view; } @@ -47,10 +48,12 @@ namespace Umbraco.Core.PropertyEditors /// from the name of the property marked with this attribute. public ConfigurationFieldAttribute(string name, string view) { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - Name = name; + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + if (view == null) throw new ArgumentNullException(nameof(view)); + if (string.IsNullOrWhiteSpace(view)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(view)); - if (string.IsNullOrWhiteSpace(view)) throw new ArgumentNullOrEmptyException(nameof(view)); + Name = name; View = view; } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs index 821f06513e..7b3be7ea5f 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs @@ -1,5 +1,4 @@ using System; -using Umbraco.Core.Exceptions; namespace Umbraco.Core.PropertyEditors { @@ -53,17 +52,17 @@ namespace Umbraco.Core.PropertyEditors /// public DataEditorAttribute(string alias, EditorType type, string name, string view) { - if ((type & ~(EditorType.PropertyValue | EditorType.MacroParameter)) > 0) - throw new ArgumentOutOfRangeException(nameof(type), $"Not a valid {typeof(EditorType)} value."); + if (alias == null) throw new ArgumentNullException(nameof(alias)); + if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias)); + if ((type & ~(EditorType.PropertyValue | EditorType.MacroParameter)) > 0) throw new ArgumentOutOfRangeException(nameof(type), type, $"Not a valid {typeof(EditorType)} value."); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + if (view == null) throw new ArgumentNullException(nameof(view)); + if (string.IsNullOrWhiteSpace(view)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(view)); + Type = type; - - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentNullOrEmptyException(nameof(alias)); Alias = alias; - - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); Name = name; - - if (string.IsNullOrWhiteSpace(view)) throw new ArgumentNullOrEmptyException(nameof(view)); View = view == NullView ? null : view; } @@ -100,8 +99,10 @@ namespace Umbraco.Core.PropertyEditors get => _valueType; set { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullOrEmptyException(nameof(value)); - if (!ValueTypes.IsValue(value)) throw new ArgumentOutOfRangeException(nameof(value), $"Not a valid {typeof(ValueTypes)} value."); + if (value == null) throw new ArgumentNullException(nameof(value)); + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(value)); + if (!ValueTypes.IsValue(value)) throw new ArgumentOutOfRangeException(nameof(value), value, $"Not a valid {typeof(ValueTypes)} value."); + _valueType = value; } } diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs new file mode 100644 index 0000000000..e8af1b0ac3 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IDataEditorWithMediaPath.cs @@ -0,0 +1,19 @@ +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Must be implemented by property editors that store media and return media paths + /// + /// + /// Currently there are only 2x core editors that do this: upload and image cropper. + /// It would be possible for developers to know implement their own media property editors whereas previously this was not possible. + /// + public interface IDataEditorWithMediaPath + { + /// + /// Returns the media path for the value stored for a property + /// + /// + /// + string GetMediaPath(object value); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs index 3e4aea4385..9f29d65ffd 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs @@ -21,4 +21,4 @@ namespace Umbraco.Core.PropertyEditors /// IEnumerable ValidateFormat(object value, string valueType, string format); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs index f8e62788c8..1b4074c96f 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs @@ -19,4 +19,4 @@ namespace Umbraco.Core.PropertyEditors /// IEnumerable ValidateRequired(object value, string valueType); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs index e405fa3a3e..8e82d694a7 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; using Umbraco.Core.Services; namespace Umbraco.Core.PropertyEditors.Validators @@ -48,8 +47,9 @@ namespace Umbraco.Core.PropertyEditors.Validators get => _regex; set { - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentNullOrEmptyException(nameof(value)); + if (value == null) throw new ArgumentNullException(nameof(value)); + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(value)); + _regex = value; } } @@ -58,7 +58,9 @@ namespace Umbraco.Core.PropertyEditors.Validators public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { if (_regex == null) + { throw new InvalidOperationException("The validator has not been configured."); + } return ValidateFormat(value, valueType, _regex); } @@ -66,9 +68,12 @@ namespace Umbraco.Core.PropertyEditors.Validators /// public IEnumerable ValidateFormat(object value, string valueType, string format) { - if (string.IsNullOrWhiteSpace(format)) throw new ArgumentNullOrEmptyException(nameof(format)); + if (format == null) throw new ArgumentNullException(nameof(format)); + if (string.IsNullOrWhiteSpace(format)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(format)); if (value == null || !new Regex(format).IsMatch(value.ToString())) + { yield return new ValidationResult(_textService.Localize("validation", "invalidPattern"), new[] { "value" }); + } } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs index c51f572817..1aa29870e5 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs @@ -35,14 +35,17 @@ namespace Umbraco.Core.PropertyEditors.Validators { if (value == null) { - yield return new ValidationResult(_textService.Localize("validation", "invalidNull"), new[] {"value"}); + yield return new ValidationResult(_textService.Localize("validation", "invalidNull"), new[] { "value" }); yield break; } if (valueType.InvariantEquals(ValueTypes.Json)) { if (value.ToString().DetectIsEmptyJson()) + { yield return new ValidationResult(_textService.Localize("validation", "invalidEmpty"), new[] { "value" }); + } + yield break; } diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index 622d81f5f2..a8e6836ca1 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; -using Umbraco.Core.Exceptions; namespace Umbraco.Core { @@ -29,10 +28,14 @@ namespace Umbraco.Core /// The declaring type. /// The field type. /// The name of the field. - /// A field getter function. - /// Occurs when is null or empty. - /// Occurs when the field does not exist. - /// Occurs when does not match the type of the field. + /// + /// A field getter function. + /// + /// fieldName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match field . type. + /// Could not find field .. public static Func EmitFieldGetter(string fieldName) { var field = GetField(fieldName); @@ -45,10 +48,14 @@ namespace Umbraco.Core /// The declaring type. /// The field type. /// The name of the field. - /// A field setter action. - /// Occurs when is null or empty. - /// Occurs when the field does not exist. - /// Occurs when does not match the type of the field. + /// + /// A field setter action. + /// + /// fieldName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match field . type. + /// Could not find field .. public static Action EmitFieldSetter(string fieldName) { var field = GetField(fieldName); @@ -61,19 +68,36 @@ namespace Umbraco.Core /// The declaring type. /// The field type. /// The name of the field. - /// A field getter and setter functions. - /// Occurs when is null or empty. - /// Occurs when the field does not exist. - /// Occurs when does not match the type of the field. + /// + /// A field getter and setter functions. + /// + /// fieldName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match field . type. + /// Could not find field .. public static (Func, Action) EmitFieldGetterAndSetter(string fieldName) { var field = GetField(fieldName); return (EmitFieldGetter(field), EmitFieldSetter(field)); } + /// + /// Gets the field. + /// + /// The type of the declaring. + /// The type of the value. + /// Name of the field. + /// + /// fieldName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match field . type. + /// Could not find field .. private static FieldInfo GetField(string fieldName) { - if (string.IsNullOrWhiteSpace(fieldName)) throw new ArgumentNullOrEmptyException(nameof(fieldName)); + if (fieldName == null) throw new ArgumentNullException(nameof(fieldName)); + if (string.IsNullOrWhiteSpace(fieldName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(fieldName)); // get the field var field = typeof(TDeclaring).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); @@ -120,13 +144,18 @@ namespace Umbraco.Core /// The property type. /// The name of the property. /// A value indicating whether the property and its getter must exist. - /// A property getter function. If is false, returns null when the property or its getter does not exist. - /// Occurs when is null or empty. - /// Occurs when the property or its getter does not exist. - /// Occurs when does not match the type of the property. + /// + /// A property getter function. If is false, returns null when the property or its getter does not exist. + /// + /// propertyName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match property . type. + /// Could not find property getter for .. public static Func EmitPropertyGetter(string propertyName, bool mustExist = true) { - if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentNullOrEmptyException(nameof(propertyName)); + if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); + if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); @@ -146,13 +175,18 @@ namespace Umbraco.Core /// The property type. /// The name of the property. /// A value indicating whether the property and its setter must exist. - /// A property setter function. If is false, returns null when the property or its setter does not exist. - /// Occurs when is null or empty. - /// Occurs when the property or its setter does not exist. - /// Occurs when does not match the type of the property. + /// + /// A property setter function. If is false, returns null when the property or its setter does not exist. + /// + /// propertyName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match property . type. + /// Could not find property setter for .. public static Action EmitPropertySetter(string propertyName, bool mustExist = true) { - if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentNullOrEmptyException(nameof(propertyName)); + if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); + if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); @@ -172,13 +206,18 @@ namespace Umbraco.Core /// The property type. /// The name of the property. /// A value indicating whether the property and its getter and setter must exist. - /// A property getter and setter functions. If is false, returns null when the property or its getter or setter does not exist. - /// Occurs when is null or empty. - /// Occurs when the property or its getter or setter does not exist. - /// Occurs when does not match the type of the property. + /// + /// A property getter and setter functions. If is false, returns null when the property or its getter or setter does not exist. + /// + /// propertyName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match property . type. + /// Could not find property getter and setter for .. public static (Func, Action) EmitPropertyGetterAndSetter(string propertyName, bool mustExist = true) { - if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentNullOrEmptyException(nameof(propertyName)); + if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); + if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); @@ -402,13 +441,17 @@ namespace Umbraco.Core /// A lambda representing the method. /// The name of the method. /// A value indicating whether the constructor must exist. - /// The method. If is false, returns null when the method does not exist. + /// + /// The method. If is false, returns null when the method does not exist. + /// + /// methodName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Occurs when does not match the method signature.. + /// Occurs when no proper method with name could be found. /// - /// The method arguments are determined by generic arguments. + /// The method arguments are determined by generic arguments. /// - /// Occurs when is null or empty. - /// Occurs when no proper method with name could be found. - /// Occurs when Occurs when does not match the method signature. public static TLambda EmitMethod(string methodName, bool mustExist = true) { return EmitMethod(typeof(TDeclaring), methodName, mustExist); @@ -421,16 +464,21 @@ namespace Umbraco.Core /// The declaring type. /// The name of the method. /// A value indicating whether the constructor must exist. - /// The method. If is false, returns null when the method does not exist. + /// + /// The method. If is false, returns null when the method does not exist. + /// + /// methodName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Occurs when does not match the method signature.. + /// Occurs when no proper method with name could be found. /// - /// The method arguments are determined by generic arguments. + /// The method arguments are determined by generic arguments. /// - /// Occurs when is null or empty. - /// Occurs when no proper method with name could be found. - /// Occurs when Occurs when does not match the method signature. public static TLambda EmitMethod(Type declaring, string methodName, bool mustExist = true) { - if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentNullOrEmptyException(nameof(methodName)); + if (methodName == null) throw new ArgumentNullException(nameof(methodName)); + if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(methodName)); var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, out var isFunction); @@ -510,17 +558,21 @@ namespace Umbraco.Core /// A lambda representing the method. /// The name of the method. /// A value indicating whether the constructor must exist. - /// The method. If is false, returns null when the method does not exist. + /// + /// The method. If is false, returns null when the method does not exist. + /// + /// methodName + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Occurs when does not match the method signature.. + /// Occurs when no proper method with name could be found. /// - /// The method arguments are determined by generic arguments. + /// The method arguments are determined by generic arguments. /// - /// Occurs when is null or empty. - /// Occurs when no proper method with name could be found. - /// Occurs when Occurs when does not match the method signature. public static TLambda EmitMethod(string methodName, bool mustExist = true) { - if (string.IsNullOrWhiteSpace(methodName)) - throw new ArgumentNullOrEmptyException(nameof(methodName)); + if (methodName == null) throw new ArgumentNullException(nameof(methodName)); + if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(methodName)); // validate lambda type var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(false, out var isFunction); diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 5b069641c4..5ceb89d7fb 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -27,6 +27,17 @@ namespace Umbraco.Core.Runtime private IFactory _factory; private RuntimeState _state; + [Obsolete("Use the ctor with all parameters instead")] + public CoreRuntime() + { + } + + public CoreRuntime(ILogger logger, IMainDom mainDom) + { + MainDom = mainDom; + Logger = logger; + } + /// /// Gets the logger. /// @@ -45,14 +56,22 @@ namespace Umbraco.Core.Runtime /// public IRuntimeState State => _state; + public IMainDom MainDom { get; private set; } + /// public virtual IFactory Boot(IRegister register) { // create and register the essential services // ie the bare minimum required to boot +#pragma warning disable CS0618 // Type or member is obsolete // loggers - var logger = Logger = GetLogger(); + // TODO: Removes this in netcore, this is purely just backwards compat ugliness + var logger = GetLogger(); + if (logger != Logger) + Logger = logger; +#pragma warning restore CS0618 // Type or member is obsolete + var profiler = Profiler = GetProfiler(); var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); @@ -125,12 +144,16 @@ namespace Umbraco.Core.Runtime Level = RuntimeLevel.Boot }; - // main dom - var mainDom = new MainDom(Logger); + // TODO: remove this in netcore, this is purely backwards compat hacks with the empty ctor + if (MainDom == null) + { + MainDom = new MainDom(Logger); + } + // create the composition composition = new Composition(register, typeLoader, ProfilingLogger, _state, configs); - composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state); // run handlers RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); @@ -140,15 +163,21 @@ namespace Umbraco.Core.Runtime Compose(composition); // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate - AcquireMainDom(mainDom); + AcquireMainDom(MainDom); // determine our runtime level DetermineRuntimeLevel(databaseFactory, ProfilingLogger); // get composers, and compose var composerTypes = ResolveComposerTypes(typeLoader); - composition.WithCollectionBuilder(); - var composers = new Composers(composition, composerTypes, ProfilingLogger); + + IEnumerable enableDisableAttributes; + using (ProfilingLogger.DebugDuration("Scanning enable/disable composer attributes")) + { + enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + } + + var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); composers.Compose(); // create the factory @@ -157,6 +186,8 @@ namespace Umbraco.Core.Runtime // create & initialize the components _components = _factory.GetInstance(); _components.Initialize(); + + } catch (Exception e) { @@ -218,13 +249,13 @@ namespace Umbraco.Core.Runtime IOHelper.SetRootDirectory(path); } - private bool AcquireMainDom(MainDom mainDom) + private bool AcquireMainDom(IMainDom mainDom) { using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { - return mainDom.Acquire(); + return mainDom.IsMainDom; } catch { @@ -301,11 +332,9 @@ namespace Umbraco.Core.Runtime protected virtual IEnumerable GetComposerTypes(TypeLoader typeLoader) => typeLoader.GetTypes(); - /// - /// Gets a logger. - /// + [Obsolete("Don't use this method, the logger should be injected into the " + nameof(CoreRuntime))] protected virtual ILogger GetLogger() - => SerilogLogger.CreateWithDefaultConfiguration(); + => Logger ?? SerilogLogger.CreateWithDefaultConfiguration(); // TODO: Remove this in netcore, this purely just backwards compat ugliness /// /// Gets a profiler. diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 5d34fe70a1..74ec70828b 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Web; using Semver; +using Umbraco.Core.Collections; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; @@ -22,7 +22,7 @@ namespace Umbraco.Core private readonly ILogger _logger; private readonly IUmbracoSettingsSection _settings; private readonly IGlobalSettings _globalSettings; - private readonly HashSet _applicationUrls = new HashSet(); + private readonly ConcurrentHashSet _applicationUrls = new ConcurrentHashSet(); private readonly Lazy _mainDom; private readonly Lazy _serverRegistrar; diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index 085a7b7a5b..dc271452e1 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using System.Web.Security; using Microsoft.AspNet.Identity; using Umbraco.Core.Configuration; -using Umbraco.Core.Exceptions; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; @@ -217,7 +216,8 @@ namespace Umbraco.Core.Security { ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentNullOrEmptyException(nameof(passwordHash)); + if (passwordHash == null) throw new ArgumentNullException(nameof(passwordHash)); + if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentException("Value can't be empty.", nameof(passwordHash)); user.PasswordHash = passwordHash; @@ -329,7 +329,7 @@ namespace Umbraco.Core.Security { ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - if (login == null) throw new ArgumentNullException("login"); + if (login == null) throw new ArgumentNullException(nameof(login)); var logins = user.Logins; var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id); @@ -348,7 +348,7 @@ namespace Umbraco.Core.Security { ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - if (login == null) throw new ArgumentNullException("login"); + if (login == null) throw new ArgumentNullException(nameof(login)); var provider = login.LoginProvider; var key = login.ProviderKey; @@ -379,7 +379,7 @@ namespace Umbraco.Core.Security public Task FindAsync(UserLoginInfo login) { ThrowIfDisposed(); - if (login == null) throw new ArgumentNullException("login"); + if (login == null) throw new ArgumentNullException(nameof(login)); //get all logins associated with the login id var result = _externalLoginService.Find(login).ToArray(); @@ -413,7 +413,8 @@ namespace Umbraco.Core.Security { ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Value cannot be null or whitespace.", "roleName"); + if (roleName == null) throw new ArgumentNullException(nameof(roleName)); + if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(roleName)); var userRole = user.Roles.SingleOrDefault(r => r.RoleId == roleName); @@ -434,7 +435,8 @@ namespace Umbraco.Core.Security { ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Value cannot be null or whitespace.", "roleName"); + if (roleName == null) throw new ArgumentNullException(nameof(roleName)); + if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(roleName)); var userRole = user.Roles.SingleOrDefault(r => r.RoleId == roleName); diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 720713e9ad..1558b0170b 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -2885,7 +2885,8 @@ namespace Umbraco.Core.Services.Implement private IContentType GetContentType(IScope scope, string contentTypeAlias) { - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(contentTypeAlias)); + if (contentTypeAlias == null) throw new ArgumentNullException(nameof(contentTypeAlias)); + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); scope.ReadLock(Constants.Locks.ContentTypes); @@ -2900,7 +2901,8 @@ namespace Umbraco.Core.Services.Implement private IContentType GetContentType(string contentTypeAlias) { - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(contentTypeAlias)); + if (contentTypeAlias == null) throw new ArgumentNullException(nameof(contentTypeAlias)); + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { @@ -3083,7 +3085,7 @@ namespace Umbraco.Core.Services.Implement var version = GetVersion(versionId); //Good ole null checks - if (content == null || version == null) + if (content == null || version == null || content.Trashed) { return new OperationResult(OperationResultType.FailedCannot, evtMsgs); } diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 705a876d83..7ae330f8f1 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -595,10 +595,9 @@ namespace Umbraco.Core.Services.Implement public TItem Copy(TItem original, string alias, string name, TItem parent) { if (original == null) throw new ArgumentNullException(nameof(original)); - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentNullOrEmptyException(nameof(alias)); - - if (parent != null && parent.HasIdentity == false) - throw new InvalidOperationException("Parent must have an identity."); + if (alias == null) throw new ArgumentNullException(nameof(alias)); + if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias)); + if (parent != null && parent.HasIdentity == false) throw new InvalidOperationException("Parent must have an identity."); // this is illegal //var originalb = (ContentTypeCompositionBase)original; diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs index bc21da15a7..5189b3422e 100644 --- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs @@ -360,7 +360,9 @@ namespace Umbraco.Core.Services.Implement new XElement("Definition", definition.Key), new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), new XElement("Mandatory", propertyType.Mandatory.ToString()), + new XElement("MandatoryMessage", propertyType.MandatoryMessage), new XElement("Validation", propertyType.ValidationRegExp), + new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage), new XElement("Description", new XCData(propertyType.Description))); genericProperties.Add(genericProperty); } @@ -487,7 +489,9 @@ namespace Umbraco.Core.Services.Implement new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), new XElement("SortOrder", propertyType.SortOrder), new XElement("Mandatory", propertyType.Mandatory.ToString()), + propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null, propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null, + propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null, propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null, new XElement("Variations", propertyType.Variations.ToString())); diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index e9fdedbf33..528d0a0bf9 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.IO; using System.Linq; using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -1340,7 +1339,8 @@ namespace Umbraco.Core.Services.Implement private IMediaType GetMediaType(string mediaTypeAlias) { - if (string.IsNullOrWhiteSpace(mediaTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(mediaTypeAlias)); + if (mediaTypeAlias == null) throw new ArgumentNullException(nameof(mediaTypeAlias)); + if (string.IsNullOrWhiteSpace(mediaTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(mediaTypeAlias)); using (var scope = ScopeProvider.CreateScope()) { @@ -1350,7 +1350,7 @@ namespace Umbraco.Core.Services.Implement var mediaType = _mediaTypeRepository.Get(query).FirstOrDefault(); if (mediaType == null) - throw new Exception($"No MediaType matching the passed in Alias: '{mediaTypeAlias}' was found"); // causes rollback // causes rollback + throw new InvalidOperationException($"No media type matched the specified alias '{mediaTypeAlias}'."); scope.Complete(); return mediaType; diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 280241e342..a64e30495b 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -1344,7 +1344,8 @@ namespace Umbraco.Core.Services.Implement private IMemberType GetMemberType(IScope scope, string memberTypeAlias) { - if (string.IsNullOrWhiteSpace(memberTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(memberTypeAlias)); + if (memberTypeAlias == null) throw new ArgumentNullException(nameof(memberTypeAlias)); + if (string.IsNullOrWhiteSpace(memberTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias)); scope.ReadLock(Constants.Locks.MemberTypes); @@ -1358,7 +1359,8 @@ namespace Umbraco.Core.Services.Implement private IMemberType GetMemberType(string memberTypeAlias) { - if (string.IsNullOrWhiteSpace(memberTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(memberTypeAlias)); + if (memberTypeAlias == null) throw new ArgumentNullException(nameof(memberTypeAlias)); + if (string.IsNullOrWhiteSpace(memberTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias)); using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { diff --git a/src/Umbraco.Core/Services/Implement/PackagingService.cs b/src/Umbraco.Core/Services/Implement/PackagingService.cs index b4dcc70a96..d85c688a0d 100644 --- a/src/Umbraco.Core/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Core/Services/Implement/PackagingService.cs @@ -57,7 +57,7 @@ namespace Umbraco.Core.Services.Implement } catch (HttpRequestException ex) { - throw new ConnectionException("An error occurring downloading the package from " + url, ex); + throw new HttpRequestException("An error occurring downloading the package from " + url, ex); } //successful diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index 0ea77dedcc..363bc72bc3 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -1,15 +1,11 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Data.Common; -using System.Data.SqlClient; -using System.Data.SqlServerCe; using System.Globalization; using System.Linq; using System.Linq.Expressions; using Umbraco.Core.Configuration; using Umbraco.Core.Events; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -17,7 +13,6 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; -using Umbraco.Core.Security; namespace Umbraco.Core.Services.Implement { @@ -107,7 +102,8 @@ namespace Umbraco.Core.Services.Implement /// private IUser CreateUserWithIdentity(string username, string email, string passwordValue, bool isApproved = true) { - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullOrEmptyException(nameof(username)); + if (username == null) throw new ArgumentNullException(nameof(username)); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); // TODO: PUT lock here!! diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 9bb61c7f2e..4451fdbba7 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -10,9 +10,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Web.Security; using Newtonsoft.Json; -using Umbraco.Core.Configuration; using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Strings; @@ -1091,7 +1089,8 @@ namespace Umbraco.Core /// The safe url segment. public static string ToUrlSegment(this string text) { - if (string.IsNullOrWhiteSpace(text)) throw new ArgumentNullOrEmptyException(nameof(text)); + if (text == null) throw new ArgumentNullException(nameof(text)); + if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text)); return Current.ShortStringHelper.CleanStringForUrlSegment(text); } @@ -1104,7 +1103,8 @@ namespace Umbraco.Core /// The safe url segment. public static string ToUrlSegment(this string text, string culture) { - if (string.IsNullOrWhiteSpace(text)) throw new ArgumentNullOrEmptyException(nameof(text)); + if (text == null) throw new ArgumentNullException(nameof(text)); + if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text)); return Current.ShortStringHelper.CleanStringForUrlSegment(text, culture); } diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/SystemLock.cs similarity index 77% rename from src/Umbraco.Core/AsyncLock.cs rename to src/Umbraco.Core/SystemLock.cs index 6dd866705e..4eaae7082b 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -21,18 +21,18 @@ namespace Umbraco.Core // been closed, the Semaphore system object is destroyed - so in any case // an iisreset should clean up everything // - internal class AsyncLock + internal class SystemLock { private readonly SemaphoreSlim _semaphore; private readonly Semaphore _semaphore2; private readonly IDisposable _releaser; private readonly Task _releaserTask; - public AsyncLock() - : this (null) + public SystemLock() + : this(null) { } - public AsyncLock(string name) + public SystemLock(string name) { // WaitOne() waits until count > 0 then decrements count // Release() increments count @@ -67,35 +67,6 @@ namespace Umbraco.Core : new NamedSemaphoreReleaser(_semaphore2); } - //NOTE: We don't use the "Async" part of this lock at all - //TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again - - //public Task LockAsync() - //{ - // var wait = _semaphore != null - // ? _semaphore.WaitAsync() - // : _semaphore2.WaitOneAsync(); - - // return wait.IsCompleted - // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - // : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), - // this, CancellationToken.None, - // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - //} - - //public Task LockAsync(int millisecondsTimeout) - //{ - // var wait = _semaphore != null - // ? _semaphore.WaitAsync(millisecondsTimeout) - // : _semaphore2.WaitOneAsync(millisecondsTimeout); - - // return wait.IsCompleted - // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - // : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), - // this, CancellationToken.None, - // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - //} - public IDisposable Lock() { if (_semaphore != null) @@ -121,14 +92,18 @@ namespace Umbraco.Core private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable { private readonly Semaphore _semaphore; - private GCHandle _handle; internal NamedSemaphoreReleaser(Semaphore semaphore) { _semaphore = semaphore; - _handle = GCHandle.Alloc(_semaphore); } + #region IDisposable Support + + // This code added to correctly implement the disposable pattern. + + private bool disposedValue = false; // To detect redundant calls + public void Dispose() { Dispose(true); @@ -137,10 +112,22 @@ namespace Umbraco.Core private void Dispose(bool disposing) { - // critical - _handle.Free(); - _semaphore.Release(); - _semaphore.Dispose(); + if (!disposedValue) + { + try + { + _semaphore.Release(); + } + finally + { + try + { + _semaphore.Dispose(); + } + catch { } + } + disposedValue = true; + } } // we WANT to release the semaphore because it's a system object, ie a critical @@ -171,6 +158,9 @@ namespace Umbraco.Core // we do NOT want the finalizer to throw - never ever } } + + #endregion + } private class SemaphoreSlimReleaser : IDisposable diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 306b764787..1cdc56d253 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -128,7 +128,7 @@ --> - + @@ -263,6 +263,7 @@ + @@ -276,6 +277,7 @@ + @@ -285,6 +287,7 @@ + @@ -686,7 +689,6 @@ - @@ -854,7 +856,7 @@ - + @@ -1071,7 +1073,6 @@ - diff --git a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs index b31fa6a8df..24fb070663 100644 --- a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Exceptions; namespace Umbraco.Core.Xml { @@ -37,7 +34,8 @@ namespace Umbraco.Core.Xml // allowed 'inline', not just at the beginning... whether or not we want to support that is up // for discussion. - if (string.IsNullOrWhiteSpace(xpathExpression)) throw new ArgumentNullOrEmptyException(nameof(xpathExpression)); + if (xpathExpression == null) throw new ArgumentNullException(nameof(xpathExpression)); + if (string.IsNullOrWhiteSpace(xpathExpression)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(xpathExpression)); if (getPath == null) throw new ArgumentNullException(nameof(getPath)); if (publishedContentExists == null) throw new ArgumentNullException(nameof(publishedContentExists)); diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index 3fd8dcaea5..2dd955086d 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -4,9 +4,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; -using Umbraco.Core.Exceptions; using Umbraco.Core.IO; namespace Umbraco.Core.Xml @@ -25,9 +23,10 @@ namespace Umbraco.Core.Xml /// public static void SetAttribute(XmlDocument xml, XmlNode n, string name, string value) { - if (xml == null) throw new ArgumentNullException("xml"); - if (n == null) throw new ArgumentNullException("n"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (xml == null) throw new ArgumentNullException(nameof(xml)); + if (n == null) throw new ArgumentNullException(nameof(n)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); if (n.Attributes == null) { @@ -229,8 +228,9 @@ namespace Umbraco.Core.Xml /// a XmlAttribute public static XmlAttribute AddAttribute(XmlDocument xd, string name, string value) { - if (xd == null) throw new ArgumentNullException("xd"); - if (string.IsNullOrEmpty(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (xd == null) throw new ArgumentNullException(nameof(xd)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); var temp = xd.CreateAttribute(name); temp.Value = value; @@ -246,8 +246,9 @@ namespace Umbraco.Core.Xml /// a XmlNode public static XmlNode AddTextNode(XmlDocument xd, string name, string value) { - if (xd == null) throw new ArgumentNullException("xd"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (xd == null) throw new ArgumentNullException(nameof(xd)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); var temp = xd.CreateNode(XmlNodeType.Element, name, ""); temp.AppendChild(xd.CreateTextNode(value)); @@ -264,9 +265,10 @@ namespace Umbraco.Core.Xml /// a XmlNode public static XmlNode SetTextNode(XmlDocument xd, XmlNode parent, string name, string value) { - if (xd == null) throw new ArgumentNullException("xd"); - if (parent == null) throw new ArgumentNullException("parent"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (xd == null) throw new ArgumentNullException(nameof(xd)); + if (parent == null) throw new ArgumentNullException(nameof(parent)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); var child = parent.SelectSingleNode(name); if (child != null) @@ -289,7 +291,8 @@ namespace Umbraco.Core.Xml { if (xd == null) throw new ArgumentNullException(nameof(xd)); if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); var child = parent.SelectSingleNode(name) ?? xd.CreateNode(XmlNodeType.Element, name, ""); child.InnerXml = value; @@ -305,8 +308,9 @@ namespace Umbraco.Core.Xml /// A XmlNode public static XmlNode AddCDataNode(XmlDocument xd, string name, string value) { - if (xd == null) throw new ArgumentNullException("xd"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (xd == null) throw new ArgumentNullException(nameof(xd)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); var temp = xd.CreateNode(XmlNodeType.Element, name, ""); temp.AppendChild(xd.CreateCDataSection(value)); @@ -323,9 +327,10 @@ namespace Umbraco.Core.Xml /// a XmlNode public static XmlNode SetCDataNode(XmlDocument xd, XmlNode parent, string name, string value) { - if (xd == null) throw new ArgumentNullException("xd"); - if (parent == null) throw new ArgumentNullException("parent"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); + if (xd == null) throw new ArgumentNullException(nameof(xd)); + if (parent == null) throw new ArgumentNullException(nameof(parent)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); var child = parent.SelectSingleNode(name); if (child != null) diff --git a/src/Umbraco.Examine/MediaIndexPopulator.cs b/src/Umbraco.Examine/MediaIndexPopulator.cs index 6dadcbe4b3..1f5b11e54f 100644 --- a/src/Umbraco.Examine/MediaIndexPopulator.cs +++ b/src/Umbraco.Examine/MediaIndexPopulator.cs @@ -10,7 +10,7 @@ namespace Umbraco.Examine /// /// Performs the data lookups required to rebuild a media index /// - public class MediaIndexPopulator : IndexPopulator + public class MediaIndexPopulator : IndexPopulator { private readonly int? _parentId; private readonly IMediaService _mediaService; @@ -69,6 +69,6 @@ namespace Umbraco.Examine pageIndex++; } while (media.Length == pageSize); } - + } } diff --git a/src/Umbraco.Examine/MediaValueSetBuilder.cs b/src/Umbraco.Examine/MediaValueSetBuilder.cs index 3839d008b3..03e7f4944b 100644 --- a/src/Umbraco.Examine/MediaValueSetBuilder.cs +++ b/src/Umbraco.Examine/MediaValueSetBuilder.cs @@ -1,9 +1,13 @@ -using Examine; +using System; +using Examine; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; using Umbraco.Core.Strings; @@ -13,14 +17,16 @@ namespace Umbraco.Examine { private readonly UrlSegmentProviderCollection _urlSegmentProviders; private readonly IUserService _userService; + private readonly ILogger _logger; public MediaValueSetBuilder(PropertyEditorCollection propertyEditors, UrlSegmentProviderCollection urlSegmentProviders, - IUserService userService) + IUserService userService, ILogger logger) : base(propertyEditors, false) { _urlSegmentProviders = urlSegmentProviders; _userService = userService; + _logger = logger; } /// @@ -29,6 +35,42 @@ namespace Umbraco.Examine foreach (var m in media) { var urlValue = m.GetUrlSegment(_urlSegmentProviders); + + var umbracoFilePath = string.Empty; + var umbracoFile = string.Empty; + + var umbracoFileSource = m.GetValue(Constants.Conventions.Media.File); + + if (umbracoFileSource.DetectIsJson()) + { + ImageCropperValue cropper = null; + try + { + cropper = JsonConvert.DeserializeObject( + m.GetValue(Constants.Conventions.Media.File)); + } + catch (Exception ex) + { + _logger.Error(ex, $"Could not Deserialize ImageCropperValue for item with key {m.Key} "); + } + + if (cropper != null) + { + umbracoFilePath = cropper.Src; + } + } + else + { + umbracoFilePath = umbracoFileSource; + } + + if (!string.IsNullOrEmpty(umbracoFilePath)) + { + // intentional dummy Uri + var uri = new Uri("https://localhost/" + umbracoFilePath); + umbracoFile = uri.Segments.Last(); + } + var values = new Dictionary> { {"icon", m.ContentType.Icon?.Yield() ?? Enumerable.Empty()}, @@ -44,7 +86,8 @@ namespace Umbraco.Examine {"urlName", urlValue?.Yield() ?? Enumerable.Empty()}, {"path", m.Path?.Yield() ?? Enumerable.Empty()}, {"nodeType", m.ContentType.Id.ToString().Yield() }, - {"creatorName", (m.GetCreatorProfile(_userService)?.Name ?? "??").Yield()} + {"creatorName", (m.GetCreatorProfile(_userService)?.Name ?? "??").Yield()}, + {UmbracoExamineIndex.UmbracoFileFieldName, umbracoFile.Yield()} }; foreach (var property in m.Properties) diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index db623ecddd..7eff1bddc2 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -49,7 +49,7 @@ - + 1.0.0-beta2-19324-01 runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Umbraco.Examine/UmbracoExamineIndex.cs b/src/Umbraco.Examine/UmbracoExamineIndex.cs index 24952050da..e1dd77b994 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndex.cs @@ -32,6 +32,7 @@ namespace Umbraco.Examine /// public const string IndexPathFieldName = SpecialFieldPrefix + "Path"; public const string NodeKeyFieldName = SpecialFieldPrefix + "Key"; + public const string UmbracoFileFieldName = "umbracoFileSrc"; public const string IconFieldName = SpecialFieldPrefix + "Icon"; public const string PublishedFieldName = SpecialFieldPrefix + "Published"; diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 2ba94d8c78..042cac1281 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -5,7 +5,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Compose; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -13,8 +12,6 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; -[assembly:DisableComposer(typeof(Umbraco.Tests.Components.ComponentTests.Composer26))] - namespace Umbraco.Tests.Components { [TestFixture] @@ -65,10 +62,11 @@ namespace Umbraco.Tests.Components public void Boot1A() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - goes away with RuntimeLevel.Unknown @@ -104,10 +102,11 @@ namespace Umbraco.Tests.Components public void Boot1B() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - stays with RuntimeLevel.Run @@ -120,10 +119,11 @@ namespace Umbraco.Tests.Components public void Boot2() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 21 is required by 20 // => reorder components accordingly @@ -135,10 +135,11 @@ namespace Umbraco.Tests.Components public void Boot3() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // i23 requires 22 // 24, 25 implement i23 @@ -152,10 +153,11 @@ namespace Umbraco.Tests.Components public void BrokenRequire() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); try { @@ -175,10 +177,11 @@ namespace Umbraco.Tests.Components public void BrokenRequired() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 13 is required by 1 @@ -196,6 +199,7 @@ namespace Umbraco.Tests.Components Terminated.Clear(); var register = MockRegister(); + var typeLoader = MockTypeLoader(); var factory = MockFactory(m => { m.Setup(x => x.TryGetInstance(It.Is(t => t == typeof (ISomeResource)))).Returns(() => new SomeResource()); @@ -210,10 +214,10 @@ namespace Umbraco.Tests.Components throw new NotSupportedException(type.FullName); }); }); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer1), typeof(Composer5), typeof(Composer5a) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Assert.IsEmpty(Composed); composers.Compose(); @@ -236,10 +240,11 @@ namespace Umbraco.Tests.Components public void Requires1() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -251,10 +256,11 @@ namespace Umbraco.Tests.Components public void Requires2A() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -267,11 +273,12 @@ namespace Umbraco.Tests.Components public void Requires2B() { var register = MockRegister(); + var typeLoader = MockTypeLoader(); var factory = MockFactory(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); var builder = composition.WithCollectionBuilder(); @@ -287,35 +294,36 @@ namespace Umbraco.Tests.Components public void WeakDependencies() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer10) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(1, Composed.Count); Assert.AreEqual(typeof(Composer10), Composed[0]); types = new[] { typeof(Composer11) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); var requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer2) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer12) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(1, Composed.Count); @@ -326,10 +334,11 @@ namespace Umbraco.Tests.Components public void DisableMissing() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -341,16 +350,18 @@ namespace Umbraco.Tests.Components public void AttributesPriorities() { var register = MockRegister(); - var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); + var typeLoader = MockTypeLoader(); + var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var types = new[] { typeof(Composer26) }; // 26 disabled by assembly attribute - var composers = new Composers(composition, types, Mock.Of()); + var types = new[] { typeof(Composer26) }; + var enableDisableAttributes = new[] { new DisableComposerAttribute(typeof(Composer26)) }; + var composers = new Composers(composition, types, enableDisableAttributes, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(0, Composed.Count); // 26 gone types = new[] { typeof(Composer26), typeof(Composer27) }; // 26 disabled by assembly attribute, enabled by 27 - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, enableDisableAttributes, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); // both @@ -367,7 +378,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = typeLoader.GetTypes().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web")); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); var requirements = composers.GetRequirements(); var report = Composers.GetComposersReport(requirements); Console.WriteLine(report); @@ -506,7 +517,6 @@ namespace Umbraco.Tests.Components public class Composer25 : TestComposerBase, IComposer23 { } - // disabled by assembly attribute public class Composer26 : TestComposerBase { } diff --git a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs index 72a55cee85..4a0c1d0f41 100644 --- a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs @@ -51,5 +51,47 @@ namespace Umbraco.Tests.CoreThings else Assert.IsFalse(value.HasFlagAny(test)); } + + [TestCase(TreeUse.None, TreeUse.None, TreeUse.None)] + [TestCase(TreeUse.None, TreeUse.Main, TreeUse.Main)] + [TestCase(TreeUse.None, TreeUse.Dialog, TreeUse.Dialog)] + [TestCase(TreeUse.None, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Main, TreeUse.None, TreeUse.Main)] + [TestCase(TreeUse.Main, TreeUse.Main, TreeUse.Main)] + [TestCase(TreeUse.Main, TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Main, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Dialog, TreeUse.None, TreeUse.Dialog)] + [TestCase(TreeUse.Dialog, TreeUse.Main, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Dialog, TreeUse.Dialog, TreeUse.Dialog)] + [TestCase(TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.None, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)] + public void SetFlagTests(TreeUse value, TreeUse flag, TreeUse expected) + { + Assert.AreEqual(expected, value.SetFlag(flag)); + } + + [TestCase(TreeUse.None, TreeUse.None, TreeUse.None)] + [TestCase(TreeUse.None, TreeUse.Main, TreeUse.None)] + [TestCase(TreeUse.None, TreeUse.Dialog, TreeUse.None)] + [TestCase(TreeUse.None, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] + [TestCase(TreeUse.Main, TreeUse.None, TreeUse.Main)] + [TestCase(TreeUse.Main, TreeUse.Main, TreeUse.None)] + [TestCase(TreeUse.Main, TreeUse.Dialog, TreeUse.Main)] + [TestCase(TreeUse.Main, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] + [TestCase(TreeUse.Dialog, TreeUse.None, TreeUse.Dialog)] + [TestCase(TreeUse.Dialog, TreeUse.Main, TreeUse.Dialog)] + [TestCase(TreeUse.Dialog, TreeUse.Dialog, TreeUse.None)] + [TestCase(TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] + [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.None, TreeUse.Main | TreeUse.Dialog)] + [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main, TreeUse.Dialog)] + [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Dialog, TreeUse.Main)] + [TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.None)] + public void UnsetFlagTests(TreeUse value, TreeUse flag, TreeUse expected) + { + Assert.AreEqual(expected, value.UnsetFlag(flag)); + } } } diff --git a/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs b/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs index ab9b2cf73d..7c3c106724 100644 --- a/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/PhysicalFileSystemTests.cs @@ -101,7 +101,7 @@ namespace Umbraco.Tests.IO Assert.AreEqual(Path.Combine(basePath, @"foo\bar.tmp"), path); // that path is invalid as it would be outside the root directory - Assert.Throws(() => _fileSystem.GetFullPath("../../foo.tmp")); + Assert.Throws(() => _fileSystem.GetFullPath("../../foo.tmp")); // a very long path, which ends up being very long, works path = Repeat("bah/bah/", 50); diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 31b00e5cf8..343994b03a 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -238,7 +238,7 @@ namespace Umbraco.Tests.IO var sfs = new PhysicalFileSystem(path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); - Assert.Throws(() => + Assert.Throws(() => { using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) ss.AddFile("../../f1.txt", ms); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs index c0b9383b57..aa88f28dc0 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return scopeProvider?.Context?.GetEnlisted(EnlistKey); } - public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, AsyncLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) + public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, SystemLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) { var scopeContext = scopeProvider.Context; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs index 447104b7cd..c452c4792a 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs @@ -305,7 +305,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private XmlDocument _xmlDocument; // supplied xml document (for tests) private volatile XmlDocument _xml; // master xml document - private readonly AsyncLock _xmlLock = new AsyncLock(); // protects _xml + private readonly SystemLock _xmlLock = new SystemLock(); // protects _xml // to be used by PublishedContentCache only // for non-preview content only diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs index 145a19872a..56c09b18ac 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs @@ -24,7 +24,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private bool _released; private Timer _timer; private DateTime _initialTouch; - private readonly AsyncLock _runLock = new AsyncLock(); // ensure we run once at a time + private readonly SystemLock _runLock = new SystemLock(); // ensure we run once at a time // note: // as long as the runner controls the runs, we know that we run once at a time, but diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 21180ce51b..eee7446823 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -621,7 +621,9 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation() { Mandatory = true, - Pattern = "xyz" + MandatoryMessage = "Please enter a value", + Pattern = "xyz", + PatternMessage = "Please match the pattern", } }; @@ -634,7 +636,9 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.DataTypeId, result.DataTypeId); Assert.AreEqual(basic.Label, result.Name); Assert.AreEqual(basic.Validation.Mandatory, result.Mandatory); + Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); + Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); } [Test] @@ -655,7 +659,9 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation() { Mandatory = true, - Pattern = "xyz" + MandatoryMessage = "Please enter a value", + Pattern = "xyz", + PatternMessage = "Please match the pattern", } }; @@ -668,7 +674,9 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.DataTypeId, result.DataTypeId); Assert.AreEqual(basic.Label, result.Name); Assert.AreEqual(basic.Validation.Mandatory, result.Mandatory); + Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); + Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); } [Test] @@ -951,7 +959,7 @@ namespace Umbraco.Tests.Models.Mapping Name = "Tab 1", SortOrder = 0, Inherited = false, - Properties = new [] + Properties = new[] { new MemberPropertyTypeBasic { @@ -965,7 +973,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1000,7 +1008,7 @@ namespace Umbraco.Tests.Models.Mapping Name = "Tab 1", SortOrder = 0, Inherited = false, - Properties = new [] + Properties = new[] { new PropertyTypeBasic { @@ -1011,7 +1019,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1052,7 +1060,7 @@ namespace Umbraco.Tests.Models.Mapping Name = "Tab 1", SortOrder = 0, Inherited = false, - Properties = new [] + Properties = new[] { new PropertyTypeBasic { @@ -1063,7 +1071,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1109,7 +1117,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1133,7 +1141,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1187,7 +1195,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1211,7 +1219,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 diff --git a/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs index 9c326b3ddc..dad2a0ea17 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using System; namespace Umbraco.Tests.Persistence.Repositories { @@ -77,7 +78,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath); partialView = new PartialView(PartialViewType.PartialView, "\\test-path-4.cshtml") { Content = "// partialView" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ { repository.Save(partialView); }); @@ -86,11 +87,11 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsNull(partialView); // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => + Assert.Throws(() => { partialView = (PartialView) repository.Get("\\test-path-4.cshtml"); // outside the filesystem, does not exist }); - Assert.Throws(() => + Assert.Throws(() => { partialView = (PartialView) repository.Get("../../packages.config"); // outside the filesystem, exists }); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs index 36c1bbdfb4..e3c316cad0 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using System.Text; using Moq; @@ -301,7 +302,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath); script = new Script("\\test-path-4.js") { Content = "// script" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ { repository.Save(script); }); @@ -310,11 +311,11 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsNull(script); // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => + Assert.Throws(() => { script = repository.Get("\\test-path-4.js"); // outside the filesystem, does not exist }); - Assert.Throws(() => + Assert.Throws(() => { script = repository.Get("../packages.config"); // outside the filesystem, exists }); diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index 6fae1d4749..f427f22796 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -1,4 +1,5 @@ -using System.Data; +using System; +using System.Data; using System.IO; using System.Linq; using System.Text; @@ -284,7 +285,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath); stylesheet = new Stylesheet("\\test-path-4.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; - Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ + Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \ { repository.Save(stylesheet); }); @@ -294,11 +295,11 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.IsNull(stylesheet); // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => + Assert.Throws(() => { stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist }); - Assert.Throws(() => + Assert.Throws(() => { stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists }); diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index a20053c295..adfb9d3b6b 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -265,7 +265,7 @@ namespace Umbraco.Tests.Published public override bool HasValue(string culture = null, string segment = null) => _hasValue; public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; public override object GetValue(string culture = null, string segment = null) => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); - public override object GetXPathValue(string culture = null, string segment = null) => throw new WontImplementException(); + public override object GetXPathValue(string culture = null, string segment = null) => throw new InvalidOperationException("This method won't be implemented."); } } } diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 6489417dc7..2f960d498d 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -4,13 +4,18 @@ using Moq; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; using Umbraco.Web.Routing; namespace Umbraco.Tests.Routing @@ -25,7 +30,17 @@ namespace Umbraco.Tests.Routing { base.SetUp(); - _mediaUrlProvider = new DefaultMediaUrlProvider(); + var logger = Mock.Of(); + var mediaFileSystemMock = Mock.Of(); + var contentSection = Mock.Of(); + var dataTypeService = Mock.Of(); + + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(new IDataEditor[] + { + new FileUploadPropertyEditor(logger, mediaFileSystemMock, contentSection), + new ImageCropperPropertyEditor(logger, mediaFileSystemMock, contentSection, dataTypeService), + })); + _mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors); } public override void TearDown() @@ -54,10 +69,10 @@ namespace Umbraco.Tests.Routing const string expected = "/media/rfeiw584/test.jpg"; var configuration = new ImageCropperConfiguration(); - var imageCropperValue = new ImageCropperValue + var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue { Src = expected - }; + }); var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); @@ -121,8 +136,8 @@ namespace Umbraco.Tests.Routing PropertyType = umbracoFilePropertyType, }; - property.SetValue("en", enMediaUrl, true); - property.SetValue("da", daMediaUrl); + property.SetSourceValue("en", enMediaUrl, true); + property.SetSourceValue("da", daMediaUrl); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; @@ -131,7 +146,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(daMediaUrl, resolvedUrl); } - private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration) + private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, string propertyValue, object dataTypeConfiguration) { var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); @@ -147,7 +162,7 @@ namespace Umbraco.Tests.Routing new SolidPublishedProperty { Alias = "umbracoFile", - SolidValue = propertyValue, + SolidSourceValue = propertyValue, SolidHasValue = true, PropertyType = umbracoFilePropertyType } diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index d0258a100f..7ab329b9a0 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Runtimes var composerTypes = typeLoader.GetTypes() // all of them .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components .Where(x => x != typeof(WebInitialComposer) && x != typeof(WebFinalComposer)); // exclude web runtime - var composers = new Composers(composition, composerTypes, profilingLogger); + var composers = new Composers(composition, composerTypes, Enumerable.Empty(), profilingLogger); composers.Compose(); // must registers stuff that WebRuntimeComponent would register otherwise @@ -272,7 +272,7 @@ namespace Umbraco.Tests.Runtimes .Where(x => !x.FullName.StartsWith("Umbraco.Tests")); // single? //var componentTypes = new[] { typeof(CoreRuntimeComponent) }; - var composers = new Composers(composition, composerTypes, profilingLogger); + var composers = new Composers(composition, composerTypes, Enumerable.Empty(), profilingLogger); // get components to compose themselves composers.Compose(); diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index 3664717af7..aacf48a636 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Tests.TestHelpers; using Umbraco.Web.Scheduling; namespace Umbraco.Tests.Scheduling @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Scheduling [OneTimeSetUp] public void InitializeFixture() { - _logger = new DebugDiagnosticsLogger(); + _logger = new ConsoleLogger(); } [Test] @@ -102,12 +103,12 @@ namespace Umbraco.Tests.Scheduling { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { - MyTask t; + MyTask t1, t2, t3; Assert.IsFalse(runner.IsRunning); // because AutoStart is false - runner.Add(new MyTask(5000)); - runner.Add(new MyTask()); - runner.Add(t = new MyTask()); + runner.Add(t1 = new MyTask(5000)); + runner.Add(t2 = new MyTask()); + runner.Add(t3 = new MyTask()); Assert.IsTrue(runner.IsRunning); // is running tasks // shutdown -force => run all queued tasks @@ -115,7 +116,7 @@ namespace Umbraco.Tests.Scheduling Assert.IsTrue(runner.IsRunning); // is running tasks await runner.StoppedAwaitable; // runner stops, within test's timeout - Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run + Assert.AreNotEqual(DateTime.MinValue, t3.Ended); // t3 has run } } @@ -124,20 +125,25 @@ namespace Umbraco.Tests.Scheduling { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { - MyTask t; + MyTask t1, t2, t3; Assert.IsFalse(runner.IsRunning); // because AutoStart is false - runner.Add(new MyTask(5000)); - runner.Add(new MyTask()); - runner.Add(t = new MyTask()); + runner.Add(t1 = new MyTask(5000)); + runner.Add(t2 = new MyTask()); + runner.Add(t3 = new MyTask()); Assert.IsTrue(runner.IsRunning); // is running tasks + Thread.Sleep(1000); // since we are forcing shutdown, we need to give it a chance to start, else it will be canceled before the queue is started + // shutdown +force => tries to cancel the current task, ignores queued tasks runner.Shutdown(true, false); // +force -wait Assert.IsTrue(runner.IsRunning); // is running that long task it cannot cancel - await runner.StoppedAwaitable; // runner stops, within test's timeout - Assert.AreEqual(DateTime.MinValue, t.Ended); // t has *not* run + await runner.StoppedAwaitable; // runner stops, within test's timeout (no cancelation token used, no need to catch OperationCanceledException) + + Assert.AreNotEqual(DateTime.MinValue, t1.Ended); // t1 *has* run + Assert.AreEqual(DateTime.MinValue, t2.Ended); // t2 has *not* run + Assert.AreEqual(DateTime.MinValue, t3.Ended); // t3 has *not* run } } @@ -163,7 +169,15 @@ namespace Umbraco.Tests.Scheduling // shutdown +force => tries to cancel the current task, ignores queued tasks runner.Shutdown(true, false); // +force -wait - await runner.StoppedAwaitable; // runner stops, within test's timeout + try + { + await runner.StoppedAwaitable; // runner stops, within test's timeout ... maybe + } + catch (OperationCanceledException) + { + // catch exception, this can occur because we are +force shutting down which will + // cancel a pending task if the queue hasn't completed in time + } } } @@ -183,25 +197,20 @@ namespace Umbraco.Tests.Scheduling runner.Terminated += (sender, args) => { terminated = true; }; Assert.IsFalse(runner.IsRunning); // because AutoStart is false - runner.Add(new MyTask(5000)); - runner.Add(new MyTask()); - runner.Add(t = new MyTask()); + runner.Add(new MyTask()); // sleeps 500 ms + runner.Add(new MyTask()); // sleeps 500 ms + runner.Add(t = new MyTask()); // sleeps 500 ms ... total = 1500 ms until it's done Assert.IsTrue(runner.IsRunning); // is running the task - runner.Stop(false); // -immediate = -force, -wait + runner.Stop(false); // -immediate = -force, -wait (max 2000 ms delay before +immediate) + await runner.TerminatedAwaitable; + + Assert.IsTrue(stopped); // raised that one Assert.IsTrue(terminating); // has raised that event - Assert.IsFalse(terminated); // but not terminated yet + Assert.IsTrue(terminated); // and that event - // all this before we await because -wait Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner - Assert.IsTrue(runner.IsRunning); // still running the task - - await runner.StoppedAwaitable; // runner stops, within test's timeout - Assert.IsFalse(runner.IsRunning); - Assert.IsTrue(stopped); - - await runner.TerminatedAwaitable; // runner terminates, within test's timeout - Assert.IsTrue(terminated); // has raised that event + Assert.IsFalse(runner.IsRunning); // done running Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run } @@ -222,23 +231,21 @@ namespace Umbraco.Tests.Scheduling runner.Terminated += (sender, args) => { terminated = true; }; Assert.IsFalse(runner.IsRunning); // because AutoStart is false - runner.Add(new MyTask(5000)); - runner.Add(new MyTask()); - runner.Add(t = new MyTask()); + runner.Add(new MyTask()); // sleeps 500 ms + runner.Add(new MyTask()); // sleeps 500 ms + runner.Add(t = new MyTask()); // sleeps 500 ms ... total = 1500 ms until it's done Assert.IsTrue(runner.IsRunning); // is running the task - runner.Stop(true); // +immediate = +force, +wait + runner.Stop(true); // +immediate = +force, +wait (no delay) + await runner.TerminatedAwaitable; + + Assert.IsTrue(stopped); // raised that one Assert.IsTrue(terminating); // has raised that event Assert.IsTrue(terminated); // and that event - Assert.IsTrue(stopped); // and that one - - // and all this before we await because +wait + Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner Assert.IsFalse(runner.IsRunning); // done running - await runner.StoppedAwaitable; // runner stops, within test's timeout - await runner.TerminatedAwaitable; // runner terminates, within test's timeout - Assert.AreEqual(DateTime.MinValue, t.Ended); // t has *not* run } } @@ -264,8 +271,7 @@ namespace Umbraco.Tests.Scheduling }, _logger)) { Assert.IsTrue(runner.IsRunning); // because AutoStart is true - runner.Stop(false); // keepalive = must be stopped - await runner.StoppedAwaitable; // runner stops, within test's timeout + await runner.StopInternal(false); // keepalive = must be stopped } } @@ -291,13 +297,19 @@ namespace Umbraco.Tests.Scheduling // dispose will stop it } - await runner.StoppedAwaitable; // runner stops, within test's timeout - //await runner.TerminatedAwaitable; // NO! see note below + try + { + await runner.StoppedAwaitable; + } + catch (OperationCanceledException) + { + // swallow this exception, it can be expected to throw since when disposing we are calling Shutdown +force + // which depending on a timing operation may cancel the cancelation token + } + + Assert.Throws(() => runner.Add(new MyTask())); - // but do NOT await on TerminatedAwaitable - disposing just shuts the runner down - // so that we don't have a runaway task in tests, etc - but it does NOT terminate - // the runner - it really is NOT a nice way to end a runner - it's there for tests } [Test] @@ -564,7 +576,7 @@ namespace Umbraco.Tests.Scheduling Thread.Sleep(1000); Assert.IsTrue(runner.IsRunning); // still waiting for the task to release Assert.IsFalse(task.HasRun); - task.Release(); + task.Release(); // unlatch var runnerTask = runner.CurrentThreadingTask; // may be null if things go fast enough if (runnerTask != null) await runnerTask; // wait for current task to complete @@ -574,7 +586,7 @@ namespace Umbraco.Tests.Scheduling } [Test] - public async Task LatchedTaskStops() + public async Task LatchedTaskStops_Runs_On_Shutdown() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { @@ -584,7 +596,7 @@ namespace Umbraco.Tests.Scheduling Thread.Sleep(5000); Assert.IsTrue(runner.IsRunning); // still waiting for the task to release Assert.IsFalse(task.HasRun); - runner.Shutdown(false, false); + runner.Shutdown(false, false); // -force, -wait await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.IsTrue(task.HasRun); } @@ -880,7 +892,9 @@ namespace Umbraco.Tests.Scheduling public override void PerformRun() { + Console.WriteLine($"Sleeping {_milliseconds}..."); Thread.Sleep(_milliseconds); + Console.WriteLine("Wake up!"); } } @@ -997,7 +1011,9 @@ namespace Umbraco.Tests.Scheduling public DateTime Ended { get; set; } public virtual void Dispose() - { } + { + + } } } } diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index 17711fbd31..b3dc274c5e 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Services public void Can_Get_Media_With_Crop_By_Path() { var mediaService = ServiceContext.MediaService; - var mediaType = MockedContentTypes.CreateImageMediaType("Image2"); + var mediaType = MockedContentTypes.CreateImageMediaTypeWithCrop("Image2"); ServiceContext.MediaTypeService.Save(mediaType); var media = MockedMedia.CreateMediaImageWithCrop(mediaType, -1); diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index cce54c81a4..a96385a923 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -865,7 +865,7 @@ namespace Umbraco.Tests.Services var userService = ServiceContext.UserService; // Act & Assert - Assert.Throws(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io")); + Assert.Throws(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io")); } [Test] diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index e93e8e8740..c55467431d 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -420,10 +420,39 @@ namespace Umbraco.Tests.TestHelpers.Entities var contentCollection = new PropertyTypeCollection(false); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + + mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + mediaType.ResetDirtyProperties(false); + + return mediaType; + } + + public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image) + { + var mediaType = new MediaType(-1) + { + Alias = alias, + Name = "Image", + Description = "ContentType used for images", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(false); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ImageCropper, ValueStorageType.Ntext) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = 1043 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index c87e6501f9..6d12ef6268 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -79,7 +79,7 @@ - + 1.8.14 diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index f7b1799d63..1653de827d 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -7,6 +7,7 @@ using Lucene.Net.Analysis; using Lucene.Net.Analysis.Standard; using Lucene.Net.Store; using Moq; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -44,11 +45,10 @@ namespace Umbraco.Tests.UmbracoExamine public static MediaIndexPopulator GetMediaIndexRebuilder(PropertyEditorCollection propertyEditors, IMediaService mediaService) { - var mediaValueSetBuilder = new MediaValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService()); + var mediaValueSetBuilder = new MediaValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService(), GetMockLogger()); var mediaIndexDataSource = new MediaIndexPopulator(null, mediaService, mediaValueSetBuilder); return mediaIndexDataSource; } - public static IContentService GetMockContentService() { long longTotalRecs; @@ -146,6 +146,11 @@ namespace Umbraco.Tests.UmbracoExamine return mediaTypeServiceMock.Object; } + public static IProfilingLogger GetMockLogger() + { + return new ProfilingLogger(Mock.Of(), Mock.Of()); + } + public static UmbracoContentIndex GetUmbracoIndexer( IProfilingLogger profilingLogger, Directory luceneDir, diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index ee63d0085e..59e8bf6c05 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -1,6 +1,14 @@ 'use strict'; module.exports = { + compile: { + build: { + sourcemaps: false + }, + dev: { + sourcemaps: true + } + }, sources: { // less files used by backoffice and preview diff --git a/src/Umbraco.Web.UI.Client/gulp/modes.js b/src/Umbraco.Web.UI.Client/gulp/modes.js new file mode 100644 index 0000000000..dc2947f2cc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/modes.js @@ -0,0 +1,13 @@ +'use strict'; + +var config = require('./config'); +var gulp = require('gulp'); + +function setDevelopmentMode(cb) { + + config.compile.current = config.compile.dev; + + return cb(); +}; + +module.exports = { setDevelopmentMode: setDevelopmentMode }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index a9d77b03c6..def956ac9f 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -259,19 +259,18 @@ function dependencies() { //css, fonts and image files var assetsTask = gulp.src(config.sources.globs.assets, { allowEmpty: true }); - if (global.isProd === true) { - assetsTask = assetsTask.pipe(imagemin([ - imagemin.gifsicle({interlaced: true}), - imagemin.jpegtran({progressive: true}), - imagemin.optipng({optimizationLevel: 5}), - imagemin.svgo({ - plugins: [ - {removeViewBox: true}, - {cleanupIDs: false} - ] - }) - ])); - } + assetsTask = assetsTask.pipe(imagemin([ + imagemin.gifsicle({interlaced: true}), + imagemin.jpegtran({progressive: true}), + imagemin.optipng({optimizationLevel: 5}), + imagemin.svgo({ + plugins: [ + {removeViewBox: true}, + {cleanupIDs: false} + ] + }) + ])); + assetsTask = assetsTask.pipe(gulp.dest(config.root + config.targets.assets)); stream.add(assetsTask); diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/removeProductionMode.js b/src/Umbraco.Web.UI.Client/gulp/tasks/removeProductionMode.js deleted file mode 100644 index 3481d84e39..0000000000 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/removeProductionMode.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -var gulp = require('gulp'); -var through2 = require('through2'); - -function createEmptyStream() { - var pass = through2.obj(); - process.nextTick(pass.end.bind(pass)); - return pass; -} - -/************************** - * Copies all angular JS files into their separate umbraco.*.js file - **************************/ -function removeProductionMode() { - - global.isProd = false; - - return createEmptyStream(); -}; - -module.exports = { removeProductionMode: removeProductionMode }; diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js index d8e4ca61d1..e3e393b661 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js @@ -23,11 +23,10 @@ module.exports = function (files, out) { // sort files in stream by path or any custom sort comparator task = task.pipe(babel()) .pipe(sort()); - - if (global.isProd === true) { - //in production, embed the templates - task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })) - } + + //in production, embed the templates + task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })) + task = task.pipe(concat(out)) .pipe(wrap('(function(){\n%= body %\n})();')) .pipe(gulp.dest(config.root + config.targets.js)); diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js index 94150043c1..e33fc0389b 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js @@ -6,25 +6,36 @@ var postcss = require('gulp-postcss'); var less = require('gulp-less'); var autoprefixer = require('autoprefixer'); var cssnano = require('cssnano'); -var cleanCss = require("gulp-clean-css"); +var cleanCss = require('gulp-clean-css'); var rename = require('gulp-rename'); +var sourcemaps = require('gulp-sourcemaps'); module.exports = function(files, out) { - + var processors = [ autoprefixer, cssnano({zindex: false}) ]; - + console.log("LESS: ", files, " -> ", config.root + config.targets.css + out) - - var task = gulp.src(files) - .pipe(less()) - .pipe(cleanCss()) - .pipe(postcss(processors)) - .pipe(rename(out)) - .pipe(gulp.dest(config.root + config.targets.css)); - + + var task = gulp.src(files); + + if(config.compile.current.sourcemaps === true) { + task = task.pipe(sourcemaps.init()); + } + + task = task.pipe(less()); + task = task.pipe(cleanCss()); + task = task.pipe(postcss(processors)); + task = task.pipe(rename(out)); + + if(config.compile.current.sourcemaps === true) { + task = task.pipe(sourcemaps.write('./maps')); + } + + task = task.pipe(gulp.dest(config.root + config.targets.css)); + return task; - + }; diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 3c1c8646fd..705c54bf04 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -10,38 +10,25 @@ * and then add the exports command to add the new item into the task menu. */ -global.isProd = true; - const { src, dest, series, parallel, lastRun } = require('gulp'); +const config = require('./gulp/config'); +const { setDevelopmentMode } = require('./gulp/modes'); const { dependencies } = require('./gulp/tasks/dependencies'); const { js } = require('./gulp/tasks/js'); const { less } = require('./gulp/tasks/less'); const { testE2e, testUnit } = require('./gulp/tasks/test'); const { views } = require('./gulp/tasks/views'); const { watchTask } = require('./gulp/tasks/watchTask'); -const { removeProductionMode } = require('./gulp/tasks/removeProductionMode'); -// Load local overwrites, can be used to overwrite paths in your local setup. -var fs = require('fs'); -var onlyScripts = require('./gulp/util/scriptFilter'); -try { - if (fs.existsSync('./gulp/overwrites/')) { - var overwrites = fs.readdirSync('./gulp/overwrites/').filter(onlyScripts); - overwrites.forEach(function(overwrite) { - require('./gulp/overwrites/' + overwrite); - }); - } -} catch (err) { - console.error(err) - } +// set default current compile mode: +config.compile.current = config.compile.build; // *********************************************************** // These Exports are the new way of defining Tasks in Gulp 4.x // *********************************************************** exports.build = series(parallel(dependencies, js, less, views), testUnit); -exports.dev = series(parallel(dependencies, js, less, views), watchTask); -exports.fastdev = series(removeProductionMode, parallel(dependencies, js, less, views), watchTask); +exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask); exports.watch = series(watchTask); // exports.runTests = series(js, testUnit); diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index a2f55b76b1..d69ef62f16 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -862,6 +862,43 @@ } } }, + "@gulp-sourcemaps/identity-map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", + "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "dev": true, + "requires": { + "acorn": "^5.0.3", + "css": "^2.2.1", + "normalize-path": "^2.1.1", + "source-map": "^0.6.0", + "through2": "^2.0.3" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true, + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -3148,6 +3185,26 @@ "which": "^1.2.9" } }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3370,6 +3427,34 @@ "ms": "^2.1.1" } }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -3637,6 +3722,12 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -6660,6 +6751,39 @@ "through2": "^2.0.1" } }, + "gulp-sourcemaps": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", + "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "1.X", + "@gulp-sourcemaps/map-sources": "1.X", + "acorn": "5.X", + "convert-source-map": "1.X", + "css": "2.X", + "debug-fabulous": "1.X", + "detect-newline": "2.X", + "graceful-fs": "4.X", + "source-map": "~0.6.0", + "strip-bom-string": "1.X", + "through2": "2.X" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "gulp-util": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", @@ -14832,6 +14956,12 @@ "strip-bom": "^2.0.0" } }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true + }, "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 44ecb4026f..2b92302684 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -66,6 +66,7 @@ "gulp-postcss": "8.0.0", "gulp-rename": "1.4.0", "gulp-sort": "2.0.0", + "gulp-sourcemaps": "^2.6.5", "gulp-watch": "5.0.1", "gulp-wrap": "0.15.0", "gulp-wrap-js": "0.4.1", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index efe65de8bf..6559e16206 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -195,6 +195,7 @@ Use this directive to construct a header inside the main editor window. @param {string=} icon Show and edit the content icon. Opens an overlay to change the icon. @param {boolean=} hideIcon Set to true to hide icon. @param {string=} alias show and edit the content alias. +@param {boolean=} aliasLocked Set to true to lock the alias. @param {boolean=} hideAlias Set to true to hide alias. @param {string=} description Add a description to the content. @param {boolean=} hideDescription Set to true to hide description. @@ -347,6 +348,7 @@ Use this directive to construct a header inside the main editor window. icon: "=", hideIcon: "@", alias: "=", + aliasLocked: "<", hideAlias: "=", description: "=", hideDescription: "@", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 06b9e51fba..31e797c6b4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -4,7 +4,7 @@ * @restrict E **/ angular.module("umbraco.directives") - .directive('umbProperty', function (umbPropEditorHelper, userService) { + .directive('umbProperty', function (userService) { return { scope: { property: "=", @@ -17,7 +17,7 @@ angular.module("umbraco.directives") templateUrl: 'views/components/property/umb-property.html', link: function (scope) { - scope.propertyEditorAPI = {}; + scope.propertyActions = []; userService.getCurrentUser().then(function (u) { var isAdmin = u.userGroups.indexOf('admin') !== -1; @@ -25,28 +25,20 @@ angular.module("umbraco.directives") }); }, //Define a controller for this directive to expose APIs to other directives - controller: function ($scope, $timeout) { + controller: function ($scope) { var self = this; - + //set the API properties/methods self.property = $scope.property; self.setPropertyError = function (errorMsg) { $scope.property.propertyErrorMessage = errorMsg; }; - - var unsubscribe = $scope.$on("ExposePropertyEditorAPI", function(event, api) { - - //avoid eventual parent properties to capture this. - event.stopPropagation(); - - $scope.propertyEditorAPI = api; - }); - $scope.$on("$destroy", function () { - unsubscribe(); - }); + self.setPropertyActions = function(actions) { + $scope.propertyActions = actions; + }; } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 2bd93a4b27..3d743c7e9a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -65,6 +65,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use vm.reloadNode = reloadNode; vm.syncTree = syncTree; vm.loadChildren = loadChildren; + vm.hasTree = hasTree; //wire up the exposed api object for hosting controllers if ($scope.api) { @@ -72,6 +73,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use $scope.api.load = vm.load; $scope.api.reloadNode = vm.reloadNode; $scope.api.syncTree = vm.syncTree; + $scope.api.hasTree = vm.hasTree; } //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should @@ -203,6 +205,25 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use }); } + //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node + function hasTree(treeAlias) { + + if (!$scope.tree) { + throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null"; + } + + if (!treeAlias) { + return false; + } + + var treeRoots = getTreeRootNodes(); + var foundTree = _.find(treeRoots, function (node) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); + }); + + return foundTree !== undefined; + } + //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node function loadActiveTree(treeAlias) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index 975b10d678..0a6eeb8835 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -90,7 +90,9 @@ angular.module("umbraco.directives") css.push("umb-tree-item--deleted"); } - if (actionNode) { + // checking the nodeType to ensure that this node and actionNode is from the same treeAlias + if (actionNode && actionNode.nodeType === node.nodeType) { + if (actionNode.id === node.id && String(node.id) !== "-1") { css.push("active"); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js index 1ddd09357a..9114cfb1c1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbconfirm.directive.js @@ -56,6 +56,7 @@ function confirmDirective() { onCancel: '=', caption: '@', confirmButtonStyle: '@', + confirmDisabled: ' .btn + .dropdown-toggle { - box-shadow: none; -} - -.btn-group > .btn + .btn { - margin-left: -6px; -} - -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px; - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn-group > .btn:last-child, .btn-group > .dropdown-toggle { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -.caret { - display: inline-block; - width: 0; - height: 0; - vertical-align: top; - border-top: 4px solid #FFFFFF; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - content: ""; - border-top: 0; - border-bottom: 4px solid #ffffff; - margin-top: 8px; - margin-left: 0; -} - -.dropdown-menu { +.menu-bar { position: absolute; - display: block; - top: auto; - right: 0; - z-index: 1000; - display: block; - float: left; - min-width: 160px; - padding: 5px 0; - margin: -96px 10px 0 0; - margin-bottom: 1px; - list-style: none; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 6px; - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - background-clip: padding-box; -} - -.dropdown-menu > li > a, -.dropdown-menu > li > button { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 20px; - color: black; - white-space: nowrap; - cursor:pointer; -} - -.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 { - color: #000000; - background: #e4e0dd; -} - -/****************************/ -/* Speech bubble */ -/****************************/ - -#speechbubble { - position: absolute; - right: 0; - bottom: 40px; + bottom:0; left: 0; - z-index: 9999; - display: none; - padding: 0; - margin: auto; - margin-left: 300px; - text-align: left; - background: none; - border: none; - border-bottom: none; -} + right: 0; + background-color: @blueExtraDark; + color:@white; -#speechbubble p { - position: relative; - padding: 8px 30px 8px 20px; - margin: auto; - margin-top: 5px; - font-size: 12px; - color: #ffffff; - text-shadow: none; - background-color: #46a546; - border: none; - border-color: transparent; - border-radius: 5px 0 0 5px; -} - - -/****************************/ -/* Main section menu */ -/****************************/ - -.more-options i { - display: inline-block; - width: 5px !important; - height: 5px !important; - margin: 5px 1px 7px 0; - background: #d9d9d9; - border-radius: 20px; -} - -.fix-left-menu { - position: fixed; - top: 0; - left: 0; - width: 80px; - height: 100%; - padding: 0; - margin-left: -80px; font-family: "Lato", Helvetica, Arial, sans-serif; - font-size: 13px; + font-size: 12px; line-height: 16px; - background: #1b264f; - transition: all 0.2s ease-in-out; - z-index: 9999; + + animation: menu-bar-animation 1.2s; + animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); + } -.avatar { - text-align:center; - padding: 27px 0 29px 0; - border-bottom: 1px solid #2E2246; -} +@keyframes menu-bar-animation { + 0% { + bottom: -50px; + } + 40% { + bottom: -50px; + } + 80% { + bottom: 0px; + } + } -.help { - position: absolute; - bottom: 0; - left: 0; - display: block; - width: 100%; - margin: 0; - font-size: 30px; - text-align: center; - color: #D8D7D9; - opacity: 0.4; - transition: all .3s linear; -} +.menu-bar__right-part { + float: right; + display: flex; + flex: row; -ul.sections { - display: block; - background: #1b264f; - position:absolute; - top: 90px; - width: 80px; - transition: all 0.2s ease-in-out; - list-style:none; - margin:0; - padding:0; - margin-left: -80px; - overflow: auto; - overflow-x: hidden; - height: calc(100% - 91px); - - &::-webkit-scrollbar { - width: 0px; - background: transparent; + > div, > button { + border-left: 1px solid rgba(255, 255, 255, .25); } } -ul.sections li { - display: block; - border-left: 4px #1b264f solid; - transition: all .3s linear; +.menu-bar__title { + display: inline-block; + padding: 11px 15px; + font-weight: bold; + font-size: 13px; +} + +.menu-bar__button { + display: inline-block; + padding: 11px 15px; + height: 40px; + border:none; + background-color: @blueExtraDark; + + text-align: left; + font: inherit; + color: inherit; cursor: pointer; -} -.fix-left-menu ul.sections li a span, -.fix-left-menu ul.sections li a i { - color: #fff; - opacity: .7; - transition: all .3s linear; -} + transition: color 120ms linear, background-color 120ms linear; + + .icon { + margin-right: 10px; + font-size: 18px; + vertical-align: middle; + } + + span { + vertical-align: middle; + } + + > svg { + display: inline-block; + width: 14px; + height: 14px; + fill: #fff; + margin-right: 10px; + vertical-align: middle; + transition: fill 120ms linear; + } -ul.sections li a { - display: block; - width: 100%; - height: 100%; - padding: 20px 4px 15px 0; - margin: 0 0 0 -4px; - text-align: center; - text-decoration: none; - border-bottom: 1px solid #2E2246; &:hover { - span, i { - opacity: 1; - color:#fff; + background-color: lighten(@blueExtraDark, 4%); + } + &.--active { + color: @pinkLight; + > svg { + fill: @pinkLight; } } } -ul.sections li a i { - font-size: 30px; - opacity: 0.8; -} +.preview-menu-option { -ul.sections li a span { - display: block; - font-size: 10px; - line-height: 1.4em; - opacity: 0.8; -} - -ul.sections li.current { - border-left: 4px #f5c1bc solid; -} - -ul.sections li.current a i { - color: #f5c1bc; -} - -ul.sections li.current { - border-left: 4px #f5c1bc solid; -} - -ul.sections li:hover a i, -ul.sections li:hover a span { - opacity: 1; -} - -.fix-left-menu:hover .help { - opacity: 1; -} - -.fix-left-menu.selected, -.sections.selected { - margin-left:0px; -} - -/*************************************************/ -/* Main panel */ -/*************************************************/ - -.main-panel { - position: fixed; - top: 0; - left: 0; - margin-left: -330px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - line-height: 16px; - background: #ffffff; - transition: all 0.2s ease-in-out; - width: 250px; - height: 100%; - padding: 0; - z-index: 999; - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); -} - -.main-panel .header { - padding: 28px 20px 32px 20px; - font-weight: bold; - background: #f8f8f8; - border-bottom: 1px solid #d9d9d9; -} - -.main-panel .header h3 { - color: rgba(179, 179, 179, 0.49); - font-size: 24px; - margin:0; -} - -.main-panel .header h3 i { - position: absolute; - right: 20px; - cursor:pointer; - transition: all 0.2s ease-in-out; -} - -.main-panel .header h3 i:hover { - color:#333; -} - -.main-panel.selected { - margin-left: 80px; - position: absolute; - right: 0; - bottom: 0; - left: 0; - overflow: auto; -} - -.main-panel .content { - padding:20px 0; -} - -/*************************************************/ -/* float-panel */ -/*************************************************/ - -.float-panel { - position: fixed; - top: 0; - z-index: 99; - width: 250px; - height: 100%; - padding: 0; - padding: 20px; - margin-left: -480px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - line-height: 16px; - background: #ffffff; - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - transition: all 0.2s ease-in-out; -} - -.float-panel.selected{ - margin-left: 0px; -} - -/*************************************************/ -/* sample palette color */ -/*************************************************/ - -.samples h4 { - position: initial; - display: block; - padding: 5px 10px; - margin: 0; - border: 0px solid #6B6B6B; - font-weight: normal; - font-size: 12px; -} - -.samples > li { - padding: 6px 20px; - cursor: pointer; -} - -.samples > li:hover, .samples > li.hover { - background: #f8f8f8; -} - -.samples > li ul { - display:table; - width:100%; -} - -.samples > li ul > li { - display: table-cell; - height: 15px; - padding: 0; -} - -/*************************************************/ -/* canvas designer panel */ -/*************************************************/ - -h4.panel-title { - cursor:pointer; - background-color: #f8f8f8; - color: #767676; -} - -.editor-category{ - margin: 5px 10px; - border: 1px solid #D9D9D9; -} - -.canvasdesigner-panel-container { - padding: 10px 10px; -} - -.canvasdesigner-panel-property { - clear: both; - overflow: hidden; - margin: 0 0 10px 0; -} - -.canvasdesigner .box-slider { - padding: 0px 0px 6px 0px; - overflow: hidden; - clear: both; -} - -.field-title { - float: left; - margin-right: 10px; - font-size: 12px; - color: #d9d9d9; -} - -.div-field { - margin-bottom: 10px; - overflow: hidden; - clear: both; -} - -/*************************************************/ -/* font family picker */ -/*************************************************/ - -.fontFamilyPickerPreview { - float: left; - width: 90%; - padding: 8px; - margin-top: 4px; - clear: both; - font-size: 18px; - color: #CDCDCD; - cursor: pointer; - border: 1px solid #CDCDCD; - text-align: center; position: relative; -} - -.fontFamilyPickerPreview span { - font-size: 32px; - line-height: 32px; -} - -.fontPickerDelete { - position: absolute; - margin: 5px 0 0 -15px; - cursor: pointer; - color:#CDCDCD; - right: 0; - top: 0; -} - -.fontFamilyPickerPreview:hover { - border: 1px solid #979797; - color:#979797; -} - -.fontFamilyPickerPreview:hover .fontPickerDelete { - color:#979797; -} - -.canvasdesigner-fontfamilypicker { - margin-bottom:30px; -} - -.canvasdesigner-fontfamilypicker select { - font-size: 12px; - padding: 2px; -} - -.canvasdesigner-fontfamilypicker select.font-list { - width:170px; -} - -.canvasdesigner-fontfamilypicker select.variant-list { - width:60px; -} - -.canvasdesigner-fontfamilypicker { - font-size: 42px; - line-height: 50px; - margin-bottom:40px; -} - -/*************************************************/ -/* slider */ -/*************************************************/ - -.canvasdesigner .ui-widget-content { - background: rgba(0, 0, 0, 0.27) !important; - border: 0 solid #fff !important; - border-radius: 1px !important; -} - -.canvasdesigner .ui-slider-horizontal { - margin: 14px 11px 0 11px; -} - -.ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default, -.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, -.ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { - border: 1px solid #535353 !important; - background: #535353 !important; - background-color:none !important; - outline:none; -} - -.canvasdesigner .ui-slider .ui-slider-handle:hover, -.canvasdesigner .ui-slider .ui-slider-handle:focus { - color: #d9d9d9 !important; -} - -.canvasdesigner .ui-slider .ui-slider-handle span { - position: absolute !important; - margin: -15px 0 0 -8px !important; - color: #535353 !important; - font-size: 9px !important; - text-align: -webkit-center !important; - display: block !important; - width: 30px !important; -} - -.canvasdesigner .slider-input { - float: right; - width: 25px; - padding: 0; - margin-top: -9px; - margin-right: 1px; - font-size: 12px; - color: #d9d9d9; - text-align: right; - background-color: transparent; - border: none; - -} - -@-moz-document url-prefix() { - .canvasdesigner .slider-input { - margin-top: -6px; - } -} - -.canvasdesigner .sp-replacer { - padding: 0; - margin: 0; - display: block; - border: none; - height: 26px; - border-radius: 1px; - border: 1px solid #CDCDCD; -} - -.canvasdesigner .sp-replacer:hover { - border: 1px solid #979797; -} - -.canvasdesigner .panel-body { - border-top: none !important; -} - -.canvasdesigner select { - font-size: 12px; -} - -.canvasdesigner .sp-dd { - display: none; -} - -.canvasdesigner .sp-preview { - width: 100%; - height: 100%; - margin-right: 0; - border: none; - display: block; -} - -.canvasdesigner .color-picker-preview { - height: 26px; - border: 1px solid #CDCDCD; - border-radius: 1px; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); - cursor: pointer; -} - -.canvasdesigner .float-panel .sp-container.sp-flat { - background-color: transparent; - border: none; - padding: 10px; - width: 100%; -} - -.canvasdesigner .float-panel .sp-container.sp-flat .sp-picker-container { - padding: 0; - margin: 0px; - margin-bottom: 10px; - border: none; - width: 100%; -} - -.canvasdesigner .float-panel .sp-container.sp-flat .sp-palette-container { - padding: 0; - height: 32px; - width: 100%; - float: left; - margin: 0; - border: none; -} - -.canvasdesigner .float-panel .sp-container.sp-flat .sp-button-container { - display: none; -} - -.colorPickerDelete { - position: absolute; - margin: -23px 0 0 0; - cursor: pointer; -} - -.borderStyleSelect -{ display: inline-block; - float: right; - width: 75px; - height: 28px; + + > .menu-bar__button { + position: relative; + } + + .dropdown-menu { + display:none; + + position: absolute; + right: 0; + bottom: 100%; + min-width: 200px; + + border-radius: 3px 3px 0 3px; + overflow: hidden; + + background-color: @blueExtraDark; + + > button { + position: relative; + display: list-item; + text-align: left; + width: 100%; + + &.--active { + &::before { + content: ''; + position: absolute; + left:0; + width: 3px; + top: 0; + bottom: 0; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + background-color: @pinkLight; + } + } + } + } + + &.--open { + z-index:1; + box-shadow: 0 5px 10px 0 rgba(0,0,0,.26); + > .menu-bar__button { + z-index: @zindexDropdown + 1; + } + .dropdown-menu { + display:block; + z-index: @zindexDropdown; + box-shadow: 0 5px 10px 0 rgba(0,0,0,.26); + } + } + } + + + /*************************************************/ /* IFrame size */ /*************************************************/ -.desktop { +#demo-iframe-wrapper { + transition: all 240ms cubic-bezier(0.165, 0.84, 0.44, 1); +} + +.fullsize { width: 100%; height: 100%; margin: 0 auto; overflow: hidden; } +.desktop { + width: 1920px; + height: 1080px; +} + .laptop { width: 1366px; height: 768px; @@ -762,13 +231,13 @@ h4.panel-title { height: 360px; } -.border { - margin: 75px auto; - background-color: #ffffff; - border-radius: 10px; +.shadow { + margin: 10px auto; + background-color: @white; + border-radius: 3px; + overflow: hidden; opacity: 1.0; - box-shadow: 0 0 0 29px #E9E9EB, 0 0 0 30px #D8D7D9; - transition: all 0.5s ease-in-out; + box-shadow: 0 5px 20px 0 rgba(0,0,0,.26); } iframe { @@ -786,251 +255,3 @@ iframe { .flip:before { transform: rotate(90deg); } - -/*************************************************/ -/* Image picker */ -/*************************************************/ - -.imagePickerPreview { - height: 20px; - text-align: center; - background-color: #fff; - padding: 6px 0 0 0; - cursor: pointer; - background-size: cover; - border-radius:1px; - border: 1px solid #CDCDCD; - color: #CDCDCD; -} - -.sp-clear-display { - background-image: none !important; -} - -.imagePickerPreview:hover { - border: 1px solid #979797; -} - -.imagePickerPreview:hover i { - color: #979797; -} - -.imagePickerPreview i { - font-size:24px; - color: #CDCDCD; -} - -.canvasdesignerImagePicker { - padding: 0; - margin-top: 10px; - overflow: auto; - text-align: left; - list-style: none; -} - -.canvasdesignerImagePicker ul { - padding: 0; - margin: 0; - margin-left: 20px; - list-style: none; -} - -.canvasdesignerImagePicker li { - display: inline-block; - margin-bottom: 10px; -} - -.canvasdesignerImagePicker ul.media-items li { - margin-right: 20px; -} - -.canvasdesignerImagePicker .media-preview { - position: relative; - display: inline-block; - width: 91px; - height: 78px; - cursor: pointer; - background-position: center center; - background-size: cover; - border: 2px solid #F3F2F2; -} - -.canvasdesignerImagePicker .media-preview .folder { - position: absolute; - top: 30px; - left: 0; - width: 100%; - font-size: 40px; - color: gainsboro; - text-align: center; -} - -.canvasdesignerImagePicker .media-preview .folder-name { - margin-top: -5px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - line-height: 0; - color: grey; -} - -.canvasdesignerImagePicker .media-preview:hover, -.media-preview.selected { - border: 2px solid #84B8F0; -} - -.canvasdesignerImagePicker .modal-dialog { - width: 618px; - font-weight: normal; -} - -.bodyCanvasdesignerImagePicker .breadcrumb { - margin-top: 4px; - margin-bottom: 10px; - font-size: 16px; - text-align: left; -} - -.bodyCanvasdesignerImagePicker .breadcrumb > li { - display: inline; -} - -.breadcrumb{ - font-size: 12px; -} - -.bodyCanvasdesignerImagePicker .breadcrumb > li a { - cursor: pointer; -} - -.bodyCanvasdesignerImagePicker .breadcrumb input { - padding: 0; - margin: -4px -4px; -} - -.bodyCanvasdesignerImagePicker .fileinput-button { - position: absolute; - top: 10px; - right: 10px; - font-size: 19px; -} - -.bodyCanvasdesignerImagePicker input.input-media { - position: absolute; - top: 0; - right: 0; - margin: 0; - font-size: 23px; - cursor: pointer; - opacity: 0; - transform: translate(-300px, 0) scale(4); - direction: ltr; -} - -.bodyCanvasdesignerImagePicker .breadcrumb a.disabled, -.bodyCanvasdesignerImagePicker .breadcrumb a.disabled:hover { - color: grey; - text-decoration: none; - cursor: default; -} - -.canvasdesignerImagePicker h3 { - font-size: 18px; - color: #555555; -} - -/*************************************************/ -/* Border editor */ -/*************************************************/ - -.box-preview { - display:inline-block; -} - -.box-preview li { - height: 30px; - width: 30px; - border-radius: 1px; - border: none; - display:inline-block; - background-color:#535353; - cursor:pointer; - margin-right: 6px; - position:relative; -} - -.box-preview li:last-child{ - margin-right: 0px; -} - -.box-preview li.selected { - border-color:#53a93f !important; -} - -.box-preview li.border-all { - border: 6px solid #b3b3b3; - height: 18px; - width: 18px; - margin-left:0px -} - -.box-preview li.border-left { - border-left: 6px solid #b3b3b3; - width: 24px; -} - -.box-preview li.border-right { - border-right: 6px solid #b3b3b3; - width: 24px; -} - -.box-preview li.border-top { - border-top: 6px solid #b3b3b3; - height: 24px; -} - -.box-preview li.border-bottom { - border-bottom: 6px solid #b3b3b3; - height: 24px; -} - -.bordereditor .color-picker-preview { - display: inline-block; - width: 120px; - float: left; -} - -/*************************************************/ -/* Radius editor */ -/*************************************************/ - -.radius-top-left, .radius-top-right, .radius-bottom-left, .radius-bottom-right { - display: block; - position: absolute; - background-color: #b3b3b3; - width: 7px; - height: 7px; -} - -.box-preview li.selected span { - background-color:#53a93f; -} - -.radius-top-left{ - top:0; - left:0; -} - -.radius-top-right{ - top:0; - right:0; -} - -.radius-bottom-left{ - bottom:0; - left:0; -} - -.radius-bottom-right{ - bottom:0; - right:0 -} diff --git a/src/Umbraco.Web.UI.Client/src/less/colors.less b/src/Umbraco.Web.UI.Client/src/less/colors.less new file mode 100644 index 0000000000..4cb70cdf5e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/colors.less @@ -0,0 +1,81 @@ +.red{color: @red;} +.blue{color: @blue;} +.black{color: @black;} +.turquoise{color: @turquoise;} +.turquoise-d1{color: @turquoise-d1;} + +.text-warning { + color: @orange; +} +.text-error { + color: @red; +} +.text-success { + color: @green; +} + + +//icon colors for tree icons +.color-red, .color-red i{color: @red-d1 !important;} +.color-blue, .color-blue i{color: @turquoise-d1 !important;} +.color-orange, .color-orange i{color: @orange !important;} +.color-green, .color-green i{color: @green-d1 !important;} +.color-yellow, .color-yellow i{color: @yellowIcon !important;} + +/* Colors based on https://zavoloklom.github.io/material-design-color-palette/colors.html */ +.btn-color-black {background-color: @black;} +.color-black i { color: @black;} + +.btn-color-blue-grey {background-color: @blueGrey;} +.color-blue-grey, .color-blue-grey i { color: @blueGrey !important;} + +.btn-color-grey{background-color: @grayIcon;} +.color-grey, .color-grey i { color: @grayIcon !important; } + +.btn-color-brown{background-color: @brownIcon;} +.color-brown, .color-brown i { color: @brownIcon !important; } + +.btn-color-blue{background-color: @blueIcon;} +.color-blue, .color-blue i { color: @blueIcon !important; } + +.btn-color-light-blue{background-color: @lightBlueIcon;} +.color-light-blue, .color-light-blue i {color: @lightBlueIcon !important;} + +.btn-color-cyan{background-color: @cyanIcon;} +.color-cyan, .color-cyan i { color: @cyanIcon !important; } + +.btn-color-green{background-color: @greenIcon;} +.color-green, .color-green i { color: @greenIcon !important; } + +.btn-color-light-green{background-color: @lightGreenIcon;} +.color-light-green, .color-light-green i {color: @lightGreenIcon !important; } + +.btn-color-lime{background-color: @limeIcon;} +.color-lime, .color-lime i { color: @limeIcon !important; } + +.btn-color-yellow{background-color: @yellowIcon;} +.color-yellow, .color-yellow i { color: @yellowIcon !important; } + +.btn-color-amber{background-color: @amberIcon;} +.color-amber, .color-amber i { color: @amberIcon !important; } + +.btn-color-orange{background-color: @orangeIcon;} +.color-orange, .color-orange i { color: @orangeIcon !important; } + +.btn-color-deep-orange{background-color: @deepOrangeIcon;} +.color-deep-orange, .color-deep-orange i { color: @deepOrangeIcon !important; } + +.btn-color-red{background-color: @redIcon;} +.color-red, .color-red i { color: @redIcon !important; } + +.btn-color-pink{background-color: @pinkIcon;} +.color-pink, .color-pink i { color: @pinkIcon !important; } + +.btn-color-purple{background-color: @purpleIcon;} +.color-purple, .color-purple i { color: @purpleIcon !important; } + +.btn-color-deep-purple{background-color: @deepPurpleIcon;} +.color-deep-purple, .color-deep-purple i { color: @deepPurpleIcon !important; } + +.btn-color-indigo{background-color: @indigoIcon;} +.color-indigo, .color-indigo i { color: @indigoIcon !important; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index a621370d02..456601a7bd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -57,7 +57,7 @@ .umb-toggle.umb-toggle--checked & { transform: translateX(20px); - background-color: white; + background-color: @white; } } @@ -75,7 +75,7 @@ .umb-toggle__icon--left { left: 5px; - color: white; + color:@white; transition: opacity 120ms; opacity: 0; // .umb-toggle:hover & { @@ -85,7 +85,7 @@ opacity: 1; } .umb-toggle.umb-toggle--checked:hover & { - color: white; + color:@white; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index 8324698685..ed80359833 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -5,7 +5,7 @@ .umb-card{ position: relative; padding: 5px 10px 5px 10px; - background: white; + background: @white; width: 100%; .title{padding: 12px; color: @gray-3; border-bottom: 1px solid @gray-8; font-weight: 400; font-size: 16px; text-transform: none; margin: 0 -10px 10px -10px;} @@ -86,7 +86,7 @@ margin: 0 auto; list-style: none; width: 100%; - + display: flex; flex-flow: row wrap; justify-content: flex-start; @@ -119,7 +119,7 @@ padding-top: 100%; border-radius: 3px; transition: background-color 120ms; - + > span { position: absolute; top: 10px; 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 44cd86a189..1217441f4e 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 @@ -17,8 +17,8 @@ } } .umb-editor-sub-header--white { - background-color: white; - border-color: white; + background-color: @white; + border-color: @white; } .umb-editor-sub-header.--state-selection { @@ -33,11 +33,11 @@ transition: box-shadow 240ms; position:sticky; z-index: 30; - + &.umb-sticky-bar--active { box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); } - + .umb-dashboard__content & { top:-20px; // umb-dashboard__content has 20px padding - offset here prevents sticky position from firing when page loads } @@ -45,8 +45,8 @@ .umb-sticky-sentinel { pointer-events: none; - z-index: 5050; - + z-index: 5050; + &.-top { height:1px; transform:translateY(-10px); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less index 9947c793c2..7036d60a63 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less @@ -44,7 +44,7 @@ display: flex; padding: 6px; margin: 10px 0px !important; - background: #F3F3F5; + background: @gray-10; cursor: move; } 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 8945d15ec6..df01477880 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 @@ -3,11 +3,11 @@ min-width: 100%; width: auto; margin-top:1px; - + .umb-tree-item__label { user-select: none; } - + &:hover .umb-tree-item__arrow { visibility: visible; cursor: pointer @@ -36,7 +36,7 @@ overflow: hidden; margin-right: 6px; } - + // Loading Animation // ------------------------ .umb-tree-item__loader { @@ -46,7 +46,7 @@ } .umb-tree-item__label { - padding: 7px 0 5px; + padding: 7px 0 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -71,7 +71,7 @@ left: 0; right: 0; bottom: 0; - border: 2px solid fade(white, 80%); + border: 2px solid fade(@white, 80%); } &:hover { @@ -86,12 +86,12 @@ .umb-tree-item.current > .umb-tree-item__inner { background: @ui-active; color:@ui-active-type; - - // override small icon color. TODO => check usage + + // override small icon color. TODO => check usage &:before { color: @blue; } - + .umb-options { &:hover i { @@ -113,5 +113,5 @@ .umb-tree-item.current-not-active > .umb-tree-item__inner { background: @ui-active-blur; - color:@ui-active-type; + color:@ui-active-type; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less index 21f4a7bda8..c6b9dc7261 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less @@ -103,7 +103,7 @@ a.umb-avatar-btn:hover { text-decoration: none; } a.umb-avatar-btn .umb-avatar { - border: 2px dashed #A2A1A6; + border: 2px dashed @gray-6; } a.umb-avatar-btn .umb-avatar span { font-size: 50px; @@ -114,4 +114,4 @@ a.umb-avatar-btn .umb-avatar span { font-size: 50px; } -/*border-radius: 50%; width: 100px; height: 100px; font-size: 50px; text-align: center; display: flex; align-items: center; justify-content: center; background-color: #F3F3F5; border: 2px dashed #A2A1A6; color: #A2A1A6;*/ +/*border-radius: 50%; width: 100px; height: 100px; font-size: 50px; text-align: center; display: flex; align-items: center; justify-content: center; background-color: @gray-10; border: 2px dashed @gray-6; color: @gray-6;*/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index 7f19c4933c..e0dee5f266 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -80,7 +80,7 @@ outline: 2px solid @inputBorderTabFocus; } .tabbing-active &.umb-form-check--checkbox &__input:checked:focus ~ .umb-form-check__state .umb-form-check__check { - border-color: white; + border-color: @white; } // add spacing between when flexed/inline, equal to the width of the input diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index 277c2bcbe8..479074fee9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -437,7 +437,7 @@ .umb-grid .umb-row.-active-child { background-color: @gray-10; - + .umb-row-title-bar { cursor: inherit; } @@ -445,7 +445,7 @@ .umb-row-title { color: @gray-3; } - + } @@ -582,10 +582,10 @@ .umb-grid .iconBox.selected { -webkit-appearance: none; - background-image: linear-gradient(to bottom,#e6e6e6,#bfbfbf); + background-image: linear-gradient(to bottom,@gray-9,@gray-7); background-repeat: repeat-x; zoom: 1; - border-color: #bfbfbf #bfbfbf #999; + border-color: @gray-7 @gray-7 @gray-6; border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25); box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); border-radius: 3px; @@ -638,9 +638,9 @@ .umb-grid .mce-toolbar { border-bottom: 1px solid @gray-7; - background-color: white; + background-color: @white; display: none; - + left: 0; right: 0; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index c51fd37fe4..f9d8772d45 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -493,11 +493,11 @@ input.umb-group-builder__group-sort-value { font-weight: bold; font-size: 14px; color: @ui-action-type; - - &:hover{ + + &:hover { text-decoration: none; - color:@ui-action-type-hover; - border-color:@ui-action-border-hover; + color: @ui-action-type-hover; + border-color: @ui-action-border-hover; } } @@ -554,7 +554,13 @@ input.umb-group-builder__group-sort-value { overflow: hidden; } - .editor-validation-pattern{ + .editor-validation-message { + min-width: 100%; + min-height: 25px; + margin-top: 4px; + } + + .editor-validation-pattern { border: 1px solid @gray-7; margin: 10px 0 0; padding: 6px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less index 34070256ce..e8a62f739d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -51,7 +51,7 @@ // Color swatch .button { border: none; - color: white; + color: @white; padding: 5px; text-align: center; text-decoration: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less index a87e7084fb..f3b53f4def 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less @@ -11,7 +11,7 @@ cursor: pointer; } -.umb-insert-code-box:hover, +.umb-insert-code-box:hover, .umb-insert-code-box.-selected { background-color: @ui-option-hover; color: @ui-action-type-hover; @@ -32,7 +32,7 @@ .umb-insert-code-box__check { width: 18px; height: 18px; - background: @gray-10;x + background: @gray-10; border-radius: 50%; display: flex; align-items: center; 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 cdc6cfcb63..9ebd6d6e5d 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 @@ -23,10 +23,10 @@ .umb-layout-selector__dropdown { position: absolute; padding: 5px; - background: #333; + background: @grayDark; z-index: 999; display: flex; - background: #fff; + background: @white; flex-wrap: wrap; flex-direction: column; transform: translate(-50%,0); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less index f6dfed63c1..ba46c68a57 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less @@ -52,7 +52,7 @@ tbody tr { background: @gray-10; - border-bottom: 1px solid #fff; + border-bottom: 1px solid @white; } th { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less index 76223589e4..8beff55b7c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less @@ -83,7 +83,7 @@ top: 0; line-height: 32px; right: 140px; - color: #fdb45c; + color: @yellow-d1; cursor: pointer; } @@ -92,7 +92,7 @@ top: 0; line-height: 32px; right: 120px; - color: #bbbabf; + color: @gray-7; cursor: pointer; } @@ -133,7 +133,7 @@ } .exception { - border-left: 4px solid #D42054; + border-left: 4px solid @red; padding: 0 10px 10px 10px; box-shadow: rgba(0,0,0,0.07) 2px 2px 10px; 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 50244c2079..05d91de9f7 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 @@ -54,7 +54,7 @@ color: @ui-selected-type; } } -.umb-media-grid__item.-selected, +.umb-media-grid__item.-selected, .umb-media-grid__item.-selectable:hover { &::before { content: ""; @@ -130,10 +130,10 @@ overflow: hidden; color: @black; white-space: nowrap; - border-top:1px solid fade(black, 4%); + border-top:1px solid fade(@black, 4%); background: fade(@white, 92%); transition: opacity 150ms; - + &:hover { text-decoration: underline; } @@ -181,7 +181,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-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 455a147395..699496f5d3 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 @@ -14,6 +14,17 @@ pointer-events: none; } +.umb-nested-content--mandatory { + /* + yeah so this is a pain, but we must be super specific in targeting the mandatory property labels, + otherwise all properties within a reqired, nested, nested content property will all appear mandatory + */ + > ng-form > .control-group > .umb-el-wrap > .control-header label:after { + content: '*'; + color: @red; + } +} + .umb-nested-content-overlay { position: absolute; top: 0; @@ -71,7 +82,7 @@ padding: 15px 5px; color:@ui-option-type; border-radius: 3px 3px 0 0; - + &:hover { color:@ui-option-type-hover; } @@ -93,7 +104,7 @@ padding-left: 30px; } } - + } .umb-nested-content__icons { @@ -134,6 +145,8 @@ .umb-nested-content__icon { + background: transparent; + border: 0 none; display: inline-block; padding: 4px; margin: 2px; @@ -213,19 +226,19 @@ display: none !important; } -.umb-nested-content__doctypepicker table input, +.umb-nested-content__doctypepicker table input, .umb-nested-content__doctypepicker table select { width: 100%; padding-right: 0; } -.umb-nested-content__doctypepicker table td.icon-navigation, +.umb-nested-content__doctypepicker table td.icon-navigation, .umb-nested-content__doctypepicker i.umb-nested-content__help-icon { vertical-align: middle; color: @gray-7; } -.umb-nested-content__doctypepicker table td.icon-navigation:hover, +.umb-nested-content__doctypepicker table td.icon-navigation:hover, .umb-nested-content__doctypepicker i.umb-nested-content__help-icon:hover { color: @gray-2; } @@ -235,44 +248,28 @@ } .umb-nested-content__placeholder { - height: 22px; - padding: 4px 6px; - border: 1px dashed #d8d7d9; + padding: 4px 6px; + border: 1px dashed @gray-8; background: 0 0; cursor: pointer; - color: #1b264f; + color: @blueExtraDark; -webkit-animation: fadeIn .5s; animation: fadeIn .5s; text-align: center; &--selected { - border: 1px solid #d8d7d9; + border: none; text-align: left; + padding: 0; } } -.umb-nested-content__placeholder-name{ - font-size: 15px; -} - .umb-nested-content__placeholder:hover { - color: #2152a3; - border-color: #2152a3; + color: @blueMid; + border-color: @blueMid; text-decoration: none; } -.umb-nested-content__placeholder-icon-holder { - width: 20px; - text-align: center; - display: inline-block; -} - -.umb-nested-content__placeholder-icon { - font-size: 18px; - vertical-align: middle; -} - - .form-horizontal .umb-nested-content--narrow .controls-row { margin-left: 40% !important; } @@ -291,7 +288,7 @@ // the attribute selector ensures the change only applies to the linkpicker overlay .form-horizontal .umb-nested-content--narrow [ng-controller*="Umbraco.Overlays.LinkPickerController"] .controls-row { margin-left:0!important; - + .umb-textarea, .umb-textstring { width:100%; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less index 3ce284870e..3f0b981ac6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less @@ -39,7 +39,7 @@ top: -15px; border-radius: 3px 3px 0 0; - + border-top-left-radius: 3px; border-top-right-radius: 3px; @@ -49,8 +49,8 @@ .box-shadow(0 5px 20px rgba(0,0,0,.3)); - background-color: white; - + background-color: @white; + } .umb-property .umb-property-actions { @@ -71,12 +71,12 @@ } .umb-property-actions__menu { - + position: absolute; z-index: 1000; display: block; - + float: left; min-width: 160px; list-style: none; @@ -85,11 +85,11 @@ border-top-left-radius: 0; margin-top:1px; - + } .umb-contextmenu-item > button { - + z-index:2;// need to stay on top of menu-toggle-open shadow. } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less index 1461d0f223..6ae92ffa4e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less @@ -1,7 +1,7 @@ .umb-range-slider.noUi-target { - background: linear-gradient(to bottom, #f5f5f5 0%, #f9f9f9 100%); + background: linear-gradient(to bottom, @grayLighter 0%, @grayLighter 100%); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); border-radius: 20px; height: 10px; 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 5e766b7578..94c0318fca 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 @@ -161,6 +161,7 @@ input.umb-table__input { line-height: 20px; color: @ui-option-type; vertical-align: bottom; + text-decoration: none; } .umb-table-body__checkicon, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less index 9ddad03b48..a612af65ef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less @@ -1,7 +1,7 @@ .umb-user-details-avatar { margin-bottom: 20px; padding-bottom: 20px; - border-bottom: 1px solid #d8d7d9; + border-bottom: 1px solid @gray-8; } div.umb-user-details-actions > div { @@ -63,6 +63,10 @@ a.umb-user-details-details__back-link { .umb-user-details-details__sidebar { flex: 0 0 @sidebarwidth; + + .umb-button{ + margin-left:0px; + } } @media (max-width: 768px) { @@ -77,7 +81,7 @@ a.umb-user-details-details__back-link { margin-bottom: 30px; margin-right: 0; } - + .umb-user-details-details__sidebar { flex: 1 1 auto; width: 100%; @@ -101,6 +105,7 @@ a.umb-user-details-details__back-link { .umb-user-details-details__information-item { margin-bottom: 10px; font-size: 13px; + margin-top:10px; } .umb-user-details-details__information-item-label { diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less index 807d0e863f..d64ffef098 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less @@ -22,8 +22,8 @@ text-align: center; display: flex; align-items: center; - border: 1px solid #d8d7d9; - background-color: #fff; + border: 1px solid @gray-8; + background-color: @white; margin: 0 0 0.5em; @media (min-width: 500px) { diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less index 4ebe1d47b0..91922300fb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less @@ -4,7 +4,7 @@ } .top-border { - border-top: 2px solid #f3f3f5; + border-top: 2px solid @gray-10; } .no-left-padding { diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less index b51bfeffa9..21ec047d41 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less @@ -106,7 +106,7 @@ .step-text { font-size: 16px; line-height: 1.5; - color: #4c4c4c; + color: @gray; margin-bottom: 20px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/footer.less b/src/Umbraco.Web.UI.Client/src/less/footer.less deleted file mode 100644 index 93fdb6ab5e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/footer.less +++ /dev/null @@ -1,2 +0,0 @@ -// Footer -// ------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index cfbb8b78ab..72abb3ba00 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -528,7 +528,8 @@ input[type="checkbox"][readonly] { .help-inline { display: inline-block; vertical-align: middle; - padding-left: 5px; + padding-top: 4px; + padding-left: 2px; } div.help { diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index bb684cd69b..238feead90 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -365,12 +365,12 @@ .usky-grid .iconBox.selected { -webkit-appearance: none; - background-image: -webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf)); - background-image: -webkit-linear-gradient(top,#e6e6e6,#bfbfbf); - background-image: linear-gradient(to bottom,#e6e6e6,#bfbfbf); + background-image: -webkit-gradient(linear,0 0,0 100%,from(@gray-9),to(@gray-7)); + background-image: -webkit-linear-gradient(top,@gray-9,@gray-7); + background-image: linear-gradient(to bottom,@gray-9,@gray-7); background-repeat: repeat-x; zoom: 1; - border-color: #bfbfbf #bfbfbf #999; + border-color: @gray-7 @gray-7 @gray-6; border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25); box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); border-radius: 3px; @@ -379,7 +379,7 @@ .usky-grid .iconBox i { font-size:16px !important; - color: #5F5F5F; + color: @gray-4; display:block; } diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index 865f015ffa..e964ed3c6f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -1,6 +1,7 @@ // Core variables and mixins @import "fonts.less"; // Loading fonts @import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "colors.less"; @import "mixins.less"; @import "buttons.less"; @import "forms.less"; @@ -133,8 +134,8 @@ legend { } input.ng-dirty.ng-invalid { - border-color: #b94a48; - color: #b94a48; + border-color: @pink; + color: @pink; } .disabled { diff --git a/src/Umbraco.Web.UI.Client/src/less/legacydialog.less b/src/Umbraco.Web.UI.Client/src/less/legacydialog.less index ca920d22c2..f1835a682c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/legacydialog.less +++ b/src/Umbraco.Web.UI.Client/src/less/legacydialog.less @@ -16,8 +16,8 @@ margin-top: 10px; height: 100%; overflow: auto; - border-top: 1px solid #ccc; - border-top: 1px solid #ccc; + border-top: 1px solid @gray-8; + border-top: 1px solid @gray-8; padding: 5px; } @@ -30,11 +30,11 @@ .umb-dialog .diff table th { padding: 5px; width: 25%; - border-bottom: 1px solid #ccc; + border-bottom: 1px solid @gray-8; } .umb-dialog .diff table td { - border-bottom: 1px solid #ccc; + border-bottom: 1px solid @gray-8; padding: 3px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index e49755338b..21b9c5c550 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -332,7 +332,7 @@ } // Gradient Bar Colors for buttons and alerts -.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff) { +.gradientBar(@primaryColor, @secondaryColor, @textColor: @white) { color: @textColor; #gradient > .vertical(@primaryColor, @secondaryColor); border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); @@ -341,7 +341,7 @@ // Gradients #gradient { - .horizontal(@startColor: #555, @endColor: #333) { + .horizontal(@startColor: @gray, @endColor: @grayDark) { background-color: @endColor; background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ @@ -350,7 +350,7 @@ background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; } - .vertical(@startColor: #555, @endColor: #333) { + .vertical(@startColor: @gray, @endColor: @grayDark) { background-color: mix(@startColor, @endColor, 60%); background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ @@ -359,7 +359,7 @@ background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; } - .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { + .directional(@startColor: @gray, @endColor: @grayDark, @deg: 45deg) { background-color: @endColor; background-repeat: repeat-x; background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ @@ -367,7 +367,7 @@ background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 } - .horizontal-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + .horizontal-three-colors(@startColor: @lightBlueIcon, @midColor: @purple-l1, @colorStop: 50%, @endColor: @pink) { background-color: mix(@midColor, @endColor, 80%); background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); background-image: -webkit-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); @@ -377,7 +377,7 @@ background-repeat: no-repeat; } - .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + .vertical-three-colors(@startColor: @lightBlueIcon, @midColor: @deepPurpleIcon, @colorStop: 50%, @endColor: @pink) { background-color: mix(@midColor, @endColor, 80%); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); @@ -386,7 +386,7 @@ background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); background-repeat: no-repeat; } - .radial(@innerColor: #555, @outerColor: #333) { + .radial(@innerColor: @gray, @outerColor: @grayDark) { background-color: @outerColor; background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); @@ -394,7 +394,7 @@ background-image: -o-radial-gradient(circle, @innerColor, @outerColor); background-repeat: no-repeat; } - .striped(@color: #555, @angle: 45deg) { + .striped(@color: @gray, @angle: 45deg) { background-color: @color; background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); @@ -404,7 +404,7 @@ } } -.checkeredBackground(@backgroundColor: #eee, @fillColor: #000, @fillOpacity: 0.25) { +.checkeredBackground(@backgroundColor: @gray-9, @fillColor: @black, @fillOpacity: 0.25) { background-image: url('data:image/svg+xml;charset=utf-8,\ \ \ @@ -435,14 +435,14 @@ // Button backgrounds // ------------------ -.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: #fff, @textColorHover: @textColor) { - +.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: @white, @textColorHover: @textColor) { + color: @textColor; border-color: @startColor @startColor darken(@startColor, 15%); border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); - + background-color: @startColor; - + .caret { border-top-color: @textColor; border-bottom-color: @textColor; @@ -453,7 +453,7 @@ color: @textColorHover; background-color: @hoverColor; } - + &.disabled, &[disabled] { color: @white; background-color: @sand-1; diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index fa23e08983..925f845c4c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -85,7 +85,7 @@ bottom: 0px; position: absolute; padding: 0px; - background: #fff; + background: @white; } .umb-dialog .umb-btn-toolbar .umb-control-group{ diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/document-type-editor.less b/src/Umbraco.Web.UI.Client/src/less/pages/document-type-editor.less deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less b/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less index 868a358d21..7c493ab18e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less @@ -1,5 +1,6 @@ @import "../fonts.less"; // Loading fonts @import "../variables.less"; // Loading variables +@import "../colors.less"; // Loading colors that were in variables abbr, address, diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index 152ea49bbd..8523fe9300 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -97,7 +97,8 @@ .history-line { width: 2px; - height: 100%; + top: 10px; + bottom: 10px; margin: 0 0 0 14px; background-color: @gray-8; position: absolute; 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 823daedf22..8301b6a753 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -1,13 +1,13 @@ // // Container styles // -------------------------------------------------- -.umb-property-editor { +.umb-property-editor { width: 100%; } .umb-property-editor-tiny { width: 60px; - + &.umb-editor-push { width:30%; min-width:0; @@ -30,7 +30,7 @@ } .umb-codeeditor{ - width: 99%; + width: 99%; } // displays property inline with preceeding @@ -139,7 +139,7 @@ margin-bottom: 0; vertical-align: middle; padding: 6px 10px; - background: #f7f7f7; + background: @gray-11; flex: 0 0 auto; } @@ -169,8 +169,8 @@ border: 1px solid @white; padding: 6px 10px; font-family: monospace; - border: 1px solid #dfdfe1; - background: #f7f7f7; + border: 1px solid @gray-8; + background: @gray-11; margin: 0 15px 0 3px; border-radius: 3px; } @@ -253,7 +253,7 @@ color: @ui-action-discreet-type-hover; border-color: @ui-action-discreet-type-hover; } - + &:focus { outline: none; .tabbing-active &:after { @@ -358,11 +358,11 @@ max-width: 100%; } .umb-mediapicker { - + .umb-sortable-thumbnails li { border:none; } - + } .umb-mediapicker .umb-sortable-thumbnails li { @@ -623,7 +623,7 @@ .imagecropper .umb-cropper__container { position: relative; margin-bottom: 10px; - max-width: 100%; + max-width: 100%; border: 1px solid @gray-10; @media (min-width: 769px) { @@ -822,7 +822,7 @@ background: @blueExtraDark; position: relative; user-select: all; - + .icon-trash { position: relative; cursor: pointer; @@ -843,7 +843,7 @@ border: none; background: @white; } - + .twitter-typeahead { margin: 10px; margin-top: 16px; diff --git a/src/Umbraco.Web.UI.Client/src/less/rte-content.less b/src/Umbraco.Web.UI.Client/src/less/rte-content.less index 5fd7bbf1c3..3fe4e52f92 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte-content.less @@ -1,4 +1,5 @@ @import "variables.less"; +@import "colors.less"; .mce-content-body .umb-macro-holder { border: 3px dotted @pinkLight; diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index 445ed7eb4a..d6d38f540a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -3,7 +3,7 @@ .umb-rte { position: relative; - + .umb-property-editor--limit-width(); } @@ -116,7 +116,7 @@ } } -.umb-rte .mce-btn.mce-active, .umb-rte .mce-btn.mce-active:active, +.umb-rte .mce-btn.mce-active, .umb-rte .mce-btn.mce-active:active, .umb-rte .mce-btn.mce-active:hover, .umb-rte .mce-btn.mce-active:focus { background: @gray-9; border-color: transparent; @@ -158,7 +158,7 @@ } .umb-grid .umb-rte { - border: 1px solid #d8d7d9; + border: 1px solid @gray-8; max-width: none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index e8f6d4ee58..6071c4a5ef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -76,11 +76,11 @@ @gray-10: #F3F3F5; @gray-11: #F6F6F7; -@sand-1: hsl(22, 18%, 84%);// added 2019 -@sand-2: hsl(22, 34%, 88%);// added 2019 -@sand-5: hsl(22, 31%, 93%);// added 2019 -@sand-6: hsl(22, 29%, 95%);// added 2019 -@sand-7: hsl(22, 26%, 97%);// added 2019 +@sand-1: #DED4CF;// added 2019 +@sand-2: #EBDED6;// added 2019 +@sand-5: #F3ECE8;// added 2019 +@sand-6: #F6F1EF;// added 2019 +@sand-7: #F9F7F5;// added 2019 // Additional Icon Colours @@ -124,7 +124,7 @@ //@u-greyLight: #f2ebe6;// added 2019 @u-white: #f9f7f4;// added 2019 -@u-black: black;// added 2019 +@u-black: @black;// added 2019 // UI colors @@ -132,7 +132,7 @@ @ui-option-type: @blueExtraDark; @ui-option-type-hover: @blueMid; -@ui-option: white; +@ui-option: @white; @ui-option-hover: @sand-7; @ui-option-disabled-type: @gray-6; @@ -161,14 +161,14 @@ @ui-light-active-type-hover: @blueMid; -@ui-action: white; +@ui-action: @white; @ui-action-hover: @sand-7; @ui-action-type: @blueExtraDark; @ui-action-type-hover: @blueMid; @ui-action-border: @blueExtraDark; @ui-action-border-hover: @blueMid; -@ui-action-discreet: white; +@ui-action-discreet: @white; @ui-action-discreet-hover: @sand-7; @ui-action-discreet-type: @blueExtraDark; @ui-action-discreet-type-hover: @blueMid; @@ -194,96 +194,11 @@ - -.red{color: @red;} -.blue{color: @blue;} -.black{color: @black;} -.turquoise{color: @turquoise;} -.turquoise-d1{color: @turquoise-d1;} - -.text-warning { - color: @orange; -} -.text-error { - color: @red; -} -.text-success { - color: @green; -} - - -//icon colors for tree icons -.color-red, .color-red i{color: @red-d1 !important;} -.color-blue, .color-blue i{color: @turquoise-d1 !important;} -.color-orange, .color-orange i{color: @orange !important;} -.color-green, .color-green i{color: @green-d1 !important;} -.color-yellow, .color-yellow i{color: @yellowIcon !important;} - -/* Colors based on https://zavoloklom.github.io/material-design-color-palette/colors.html */ -.btn-color-black {background-color: @black;} -.color-black i { color: @black;} - -.btn-color-blue-grey {background-color: @blueGrey;} -.color-blue-grey, .color-blue-grey i { color: @blueGrey !important;} - -.btn-color-grey{background-color: @grayIcon;} -.color-grey, .color-grey i { color: @grayIcon !important; } - -.btn-color-brown{background-color: @brownIcon;} -.color-brown, .color-brown i { color: @brownIcon !important; } - -.btn-color-blue{background-color: @blueIcon;} -.color-blue, .color-blue i { color: @blueIcon !important; } - -.btn-color-light-blue{background-color: @lightBlueIcon;} -.color-light-blue, .color-light-blue i {color: @lightBlueIcon !important;} - -.btn-color-cyan{background-color: @cyanIcon;} -.color-cyan, .color-cyan i { color: @cyanIcon !important; } - -.btn-color-green{background-color: @greenIcon;} -.color-green, .color-green i { color: @greenIcon !important; } - -.btn-color-light-green{background-color: @lightGreenIcon;} -.color-light-green, .color-light-green i {color: @lightGreenIcon !important; } - -.btn-color-lime{background-color: @limeIcon;} -.color-lime, .color-lime i { color: @limeIcon !important; } - -.btn-color-yellow{background-color: @yellowIcon;} -.color-yellow, .color-yellow i { color: @yellowIcon !important; } - -.btn-color-amber{background-color: @amberIcon;} -.color-amber, .color-amber i { color: @amberIcon !important; } - -.btn-color-orange{background-color: @orangeIcon;} -.color-orange, .color-orange i { color: @orangeIcon !important; } - -.btn-color-deep-orange{background-color: @deepOrangeIcon;} -.color-deep-orange, .color-deep-orange i { color: @deepOrangeIcon !important; } - -.btn-color-red{background-color: @redIcon;} -.color-red, .color-red i { color: @redIcon !important; } - -.btn-color-pink{background-color: @pinkIcon;} -.color-pink, .color-pink i { color: @pinkIcon !important; } - -.btn-color-purple{background-color: @purpleIcon;} -.color-purple, .color-purple i { color: @purpleIcon !important; } - -.btn-color-deep-purple{background-color: @deepPurpleIcon;} -.color-deep-purple, .color-deep-purple i { color: @deepPurpleIcon !important; } - -.btn-color-indigo{background-color: @indigoIcon;} -.color-indigo, .color-indigo i { color: @indigoIcon !important; } - - - // Scaffolding // ------------------------- @appHeaderHeight: 55px; @bodyBackground: @gray-10; -@textColor: #000; +@textColor: @black; @editorHeaderHeight: 70px; @editorFooterHeight: 50px; diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index c4cb821818..dc40338d01 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -7,6 +7,31 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi .controller("previewController", function ($scope, $window, $location) { + $scope.currentCulture = {iso:'', title:'...', icon:'icon-loading'} + var cultures = []; + + $scope.tabbingActive = false; + // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution. + // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 + function handleFirstTab(evt) { + if (evt.keyCode === 9) { + $scope.tabbingActive = true; + $scope.$digest(); + window.removeEventListener('keydown', handleFirstTab); + window.addEventListener('mousedown', disableTabbingActive); + } + } + + function disableTabbingActive(evt) { + $scope.tabbingActive = false; + $scope.$digest(); + window.removeEventListener('mousedown', disableTabbingActive); + window.addEventListener("keydown", handleFirstTab); + } + + window.addEventListener("keydown", handleFirstTab); + + //gets a real query string value function getParameterByName(name, url) { if (!url) url = $window.location.href; @@ -75,15 +100,55 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.valueAreLoaded = false; $scope.devices = [ - { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, - { name: "laptop - 1366px", css: "laptop border", icon: "icon-laptop", title: "Laptop" }, - { name: "iPad portrait - 768px", css: "iPad-portrait border", icon: "icon-ipad", title: "Tablet portrait" }, - { name: "iPad landscape - 1024px", css: "iPad-landscape border", icon: "icon-ipad flip", title: "Tablet landscape" }, - { name: "smartphone portrait - 480px", css: "smartphone-portrait border", icon: "icon-iphone", title: "Smartphone portrait" }, - { name: "smartphone landscape - 320px", css: "smartphone-landscape border", icon: "icon-iphone flip", title: "Smartphone landscape" } + { name: "fullsize", css: "fullsize", icon: "icon-application-window-alt", title: "Browser" }, + { name: "desktop", css: "desktop shadow", icon: "icon-display", title: "Desktop" }, + { name: "laptop - 1366px", css: "laptop shadow", icon: "icon-laptop", title: "Laptop" }, + { name: "iPad portrait - 768px", css: "iPad-portrait shadow", icon: "icon-ipad", title: "Tablet portrait" }, + { name: "iPad landscape - 1024px", css: "iPad-landscape shadow", icon: "icon-ipad flip", title: "Tablet landscape" }, + { name: "smartphone portrait - 480px", css: "smartphone-portrait shadow", icon: "icon-iphone", title: "Smartphone portrait" }, + { name: "smartphone landscape - 320px", css: "smartphone-landscape shadow", icon: "icon-iphone flip", title: "Smartphone landscape" } ]; $scope.previewDevice = $scope.devices[0]; + $scope.sizeOpen = false; + $scope.cultureOpen = false; + + $scope.toggleSizeOpen = function() { + $scope.sizeOpen = toggleMenu($scope.sizeOpen); + } + $scope.toggleCultureOpen = function() { + $scope.cultureOpen = toggleMenu($scope.cultureOpen); + } + + function toggleMenu(isCurrentlyOpen) { + if (isCurrentlyOpen === false) { + closeOthers(); + return true; + } else { + return false; + } + } + function closeOthers() { + $scope.sizeOpen = false; + $scope.cultureOpen = false; + } + + $scope.windowClickHandler = function() { + closeOthers(); + } + function windowBlurHandler() { + closeOthers(); + $scope.$digest(); + } + + var win = angular.element($window); + + win.on("blur", windowBlurHandler); + + $scope.$on("$destroy", function () { + win.off("blur", handleBlwindowBlurHandlerur ); + }); + function setPageUrl(){ $scope.pageId = $location.search().id || getParameterByName("id"); @@ -123,6 +188,8 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.onFrameLoaded = function (iframe) { $scope.frameLoaded = true; configureSignalR(iframe); + + $scope.currentCultureIso = $location.search().culture || null; }; /*****************************************************************************/ @@ -136,17 +203,32 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi /*****************************************************************************/ /* Change culture */ /*****************************************************************************/ - $scope.changeCulture = function (culture) { - if($location.search().culture !== culture){ + $scope.changeCulture = function (iso) { + if($location.search().culture !== iso) { $scope.frameLoaded = false; - $location.search("culture", culture); + $scope.currentCultureIso = iso; + $location.search("culture", iso); setPageUrl(); } }; - - $scope.isCurrentCulture = function(culture) { - return $location.search().culture === culture; + $scope.registerCulture = function(iso, title, isDefault) { + var cultureObject = {iso: iso, title: title, isDefault: isDefault}; + cultures.push(cultureObject); } + + $scope.$watch("currentCultureIso", function(oldIso, newIso) { + // if no culture is selected, we will pick the default one: + if ($scope.currentCultureIso === null) { + $scope.currentCulture = cultures.find(function(culture) { + return culture.isDefault === true; + }) + return; + } + $scope.currentCulture = cultures.find(function(culture) { + return culture.iso === $scope.currentCultureIso; + }) + }); + }) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index fab6ba4069..4474390199 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -81,39 +81,55 @@
- +
- -
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 47a6af17be..d72e977010 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 @@ -46,6 +46,7 @@ title="{{historyLabel}}"> - +
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 927f677463..46660fc685 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 @@ -21,7 +21,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html index 384c5ccaf7..d8e4f09b8a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html @@ -14,6 +14,7 @@ action="confirm()" button-style="{{confirmButtonStyle || 'primary'}}" state="confirmButtonState" + disabled="confirmDisabled === true" label-key="{{confirmLabelKey || 'general_ok'}}"> diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardvideos.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardvideos.html index 83f3fb2f7a..a7f7d45087 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardvideos.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardvideos.html @@ -4,13 +4,15 @@ -

Hours of Umbraco training videos are only a click away

-

Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

+

Hours of Umbraco training videos are only a click away

+ +

Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

+
-

To get you started:

+

To get you started:

  • diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html index f819a0a142..632127e38c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html @@ -100,8 +100,12 @@
    -
    Search
    -
    Search the index and view the results
    +
    + Search +
    +
    + Search the index and view the results +
    @@ -225,7 +229,7 @@
    {{vm.selectedIndex.healthStatus}}
    - The index cannot be read and will need to be rebuilt + The index cannot be read and will need to be rebuilt
    @@ -377,13 +381,14 @@
    - The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation + The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation
    - This index cannot be rebuilt because it has no assigned IIndexPopulator + This index cannot be rebuilt because it has no assigned + IIndexPopulator
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/nucache.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/nucache.html index 0b91959402..0b14e9c3ff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/nucache.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/nucache.html @@ -4,12 +4,14 @@

- (wait) + (wait)

-
Published Cache Status
+
+ Published Cache Status +
@@ -20,7 +22,9 @@
- +
@@ -31,53 +35,75 @@
-
Caches
+
+ Caches +
-
Memory Cache
+
+ Memory Cache +
- This button lets you reload the in-memory cache, by entirely reloading it from the database - cache (but it does not rebuild that database cache). This is relatively fast. - Use it when you think that the memory cache has not been properly refreshed, after some events - triggered—which would indicate a minor Umbraco issue. - (note: triggers the reload on all servers in an LB environment). + + This button lets you reload the in-memory cache, by entirely reloading it from the database + cache (but it does not rebuild that database cache). This is relatively fast. + Use it when you think that the memory cache has not been properly refreshed, after some events + triggered—which would indicate a minor Umbraco issue. + (note: triggers the reload on all servers in an LB environment). + +
- +
-
Database Cache
+
+ Database Cache +
- This button lets you rebuild the database cache, ie the content of the cmsContentNu table. - Rebuilding can be expensive. - Use it when reloading is not enough, and you think that the database cache has not been - properly generated—which would indicate some critical Umbraco issue. + + This button lets you rebuild the database cache, ie the content of the cmsContentNu table. + Rebuilding can be expensive. + Use it when reloading is not enough, and you think that the database cache has not been + properly generated—which would indicate some critical Umbraco issue. +
- +
-
Internals
+
+ Internals +
- This button lets you trigger a NuCache snapshots collection (after running a fullCLR GC). - Unless you know what that means, you probably do not need to use it. + + This button lets you trigger a NuCache snapshots collection (after running a fullCLR GC). + Unless you know what that means, you probably do not need to use it. + +
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html index e55dbe2298..6bff0bba9b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html @@ -6,39 +6,51 @@ -

Performance profiling

+

+ Performance profiling +

-

- Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. -

-

- If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. -

-

- If you want the profiler to be activated by default for all page renderings, you can use the toggle below. - It will set a cookie in your browser, which then activates the profiler automatically. - In other words, the profiler will only be active by default in your browser - not everyone else's. -

+ +

+ Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. +

+

+ If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. +

+

+ If you want the profiler to be activated by default for all page renderings, you can use the toggle below. + It will set a cookie in your browser, which then activates the profiler automatically. + In other words, the profiler will only be active by default in your browser - not everyone else's. +

+
- +
-

Friendly reminder

-

- You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. -

+

+ Friendly reminder +

+ +

+ You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. +

+
-

- Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. -

-

- Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. -

+ +

+ Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. +

+

+ Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. +

+
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index cc3e57bdc4..0ad88b9894 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -1,14 +1,30 @@ -

Start here

-

This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section:

-
Find out more:
+

+ Start here +

+ +

This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section:

+
: +
+ Find out more: +
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardvideos.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardvideos.html index 64a9c819ab..96d015f758 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardvideos.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardvideos.html @@ -1,12 +1,18 @@ -

Hours of Umbraco training videos are only a click away

-

Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

+

+ Hours of Umbraco training videos are only a click away +

+ +

Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

+
-

To get you started:

+

+ To get you started: +

  • diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js index c6c58d9fa6..4542cde343 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.delete.controller.js @@ -51,6 +51,12 @@ function DataTypeDeleteController($scope, dataTypeResource, treeService, navigat navigationService.hideDialog(); }; + vm.onReferenceClicked = function(event) { + if (event.metaKey !== true) { + navigationService.hideDialog(); + } + }; + vm.labels = {}; localizationService .localize("editdatatype_acceptDeleteConsequence", [$scope.currentNode.name]) diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index 15fb103ecd..474b07d12c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -24,10 +24,6 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic alias: "selectedEditor", description: "Select a property editor", label: "Property editor" - }, - selectedEditorId: { - alias: "selectedEditorId", - label: "Property editor alias" } }; @@ -205,7 +201,7 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic var labelKeys = [ "general_settings", - "references_tabName" + "general_info" ]; localizationService.localizeMany(labelKeys).then(function (values) { @@ -220,9 +216,9 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic }, { "name": values[1], - "alias": "references", - "icon": "icon-molecular-network", - "view": "views/datatypes/views/datatype.references.html" + "alias": "info", + "icon": "icon-info", + "view": "views/datatypes/views/datatype.info.html" } ]; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html index bc201e2c39..bdce1723a0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html @@ -50,7 +50,7 @@ - {{::relation.name}} + {{::relation.name}}

    {{::property.name}}

    @@ -74,7 +74,7 @@ - {{::relation.name}} + {{::relation.name}}

    {{::property.name}}

    @@ -98,7 +98,7 @@ - {{::relation.name}} + {{::relation.name}}

    {{::property.name}}

    diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.references.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.controller.js similarity index 75% rename from src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.references.controller.js rename to src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.controller.js index a152e2e86f..d4aff871a2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.references.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.controller.js @@ -1,12 +1,12 @@ /** * @ngdoc controller - * @name Umbraco.Editors.DataType.ReferencesController + * @name Umbraco.Editors.DataType.InfoController * @function * * @description - * The controller for the references view of the datatype editor + * The controller for the info view of the datatype editor */ -function DataTypeReferencesController($scope, $routeParams, dataTypeResource, eventsService, $timeout) { +function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout) { var vm = this; var evts = []; @@ -34,7 +34,7 @@ function DataTypeReferencesController($scope, $routeParams, dataTypeResource, ev // load data type references when the references tab is activated evts.push(eventsService.on("app.tabChange", function (event, args) { $timeout(function () { - if (args.alias === "references") { + if (args.alias === "info") { loadRelations(); } }); @@ -52,4 +52,4 @@ function DataTypeReferencesController($scope, $routeParams, dataTypeResource, ev } -angular.module("umbraco").controller("Umbraco.Editors.DataType.ReferencesController", DataTypeReferencesController); +angular.module("umbraco").controller("Umbraco.Editors.DataType.InfoController", DataTypeInfoController); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.html new file mode 100644 index 0000000000..16b2d4b263 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.info.html @@ -0,0 +1,140 @@ +
    + +
    +
    + + + + + + + + + + This Data Type has no references. + + + + + +
    + + + +
    + +
    + +
    + +
    +
    +
    +
    +
    Name
    +
    Alias
    +
    Used in
    +
    Open
    +
    +
    +
    +
    +
    +
    {{::reference.name}}
    +
    {{::reference.alias}}
    +
    {{::reference.properties | umbCmsJoinArray:', ':'name'}}
    + +
    +
    +
    +
    + + + +
    + +
    + +
    + +
    +
    +
    +
    +
    Name
    +
    Alias
    +
    Used in
    +
    Open
    +
    +
    +
    +
    +
    +
    {{::reference.name}}
    +
    {{::reference.alias}}
    +
    {{::reference.properties | umbCmsJoinArray:', ':'name'}}
    + +
    +
    +
    + +
    + + + +
    + +
    + +
    + +
    +
    +
    +
    +
    Name
    +
    Alias
    +
    Used in
    +
    Open
    +
    +
    +
    +
    +
    +
    {{::reference.name}}
    +
    {{::reference.alias}}
    +
    {{::reference.properties | umbCmsJoinArray:', ':'name'}}
    + +
    +
    +
    + +
    + + +
    + +
    + +
    + + + + + +
    {{model.content.id}}
    + {{model.content.key}} +
    + + +
    {{model.content.selectedEditor}}
    +
    + +
    +
    +
    + +
    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.references.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.references.html deleted file mode 100644 index dfa633ad16..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.references.html +++ /dev/null @@ -1,112 +0,0 @@ -
    - - - - - - - - This Data Type has no references. - - - - - -
    - - - -
    - -
    - -
    - -
    -
    -
    -
    -
    Name
    -
    Alias
    -
    Used in
    -
    Open
    -
    -
    -
    -
    -
    -
    {{::reference.name}}
    -
    {{::reference.alias}}
    -
    {{::reference.properties | umbCmsJoinArray:', ':'name'}}
    - -
    -
    -
    -
    - - - -
    - -
    - -
    - -
    -
    -
    -
    -
    Name
    -
    Alias
    -
    Used in
    -
    Open
    -
    -
    -
    -
    -
    -
    {{::reference.name}}
    -
    {{::reference.alias}}
    -
    {{::reference.properties | umbCmsJoinArray:', ':'name'}}
    - -
    -
    -
    -
    - - - -
    - -
    - -
    - -
    -
    -
    -
    -
    Name
    -
    Alias
    -
    Used in
    -
    Open
    -
    -
    -
    -
    -
    -
    {{::reference.name}}
    -
    {{::reference.alias}}
    -
    {{::reference.properties | umbCmsJoinArray:', ':'name'}}
    - -
    -
    -
    - -
    - - -
    - - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.settings.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.settings.html index d37aa2fcd0..69a485e5be 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.settings.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/views/datatype.settings.html @@ -2,11 +2,6 @@ - -
    {{model.content.id}}
    - {{model.content.key}} -
    -
    - - + + + - - + + + +
    +

    {{mandatoryMessage}}

    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.controller.js new file mode 100644 index 0000000000..7ed7531bc5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.controller.js @@ -0,0 +1,10 @@ +function emailController($scope, validationMessageService) { + + // Set the message to use for when a mandatory field isn't completed. + // Will either use the one provided on the property type or a localised default. + validationMessageService.getMandatoryMessage($scope.model.validation).then(function(value) { + $scope.mandatoryMessage = value; + }); + +} +angular.module('umbraco').controller("Umbraco.PropertyEditors.EmailController", emailController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html index 881ad37d7c..26ec22df8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/email/email.html @@ -1,4 +1,4 @@ -
    +
    - - Required - Invalid email - {{emailFieldForm.textbox.errorMsg}} - +
    +

    {{mandatoryMessage}}

    +

    Invalid email

    +

    {{emailFieldForm.textbox.errorMsg}}

    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 7921da9c22..3db1221f5e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -673,6 +673,7 @@ angular.module("umbraco") return ((spans / $scope.model.config.items.columns) * 100).toFixed(8); }; + $scope.clearPrompt = function (scopedObject, e) { scopedObject.deletePrompt = false; e.preventDefault(); @@ -692,8 +693,15 @@ angular.module("umbraco") }; $scope.getTemplateName = function (control) { - if (control.editor.nameExp) return control.editor.nameExp(control) - return control.editor.name; + var templateName = control.editor.name; + if (control.editor.nameExp) { + var valueOfTemplate = control.editor.nameExp(control); + if (valueOfTemplate != "") { + templateName += ": "; + templateName += valueOfTemplate; + } + } + return templateName; } // ********************************************* diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html index 3995ed703d..e889067321 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html @@ -95,7 +95,7 @@
    - +
    - + 1) { - value.name += " (" + value.alias + ")"; - } - }); - }); - - } - - - $scope.add = function () { - $scope.model.value.push({ - // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition. - // For good measure we'll also prefix the tab alias "nc" - ncAlias: "", - ncTabAlias: "", - nameTemplate: "" - }); - } - - $scope.canAdd = function () { - return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length; - } - - $scope.remove = function (index) { - $scope.model.value.splice(index, 1); - } - - $scope.sortableOptions = { - axis: "y", - cursor: "move", - handle: ".handle", - placeholder: 'sortable-placeholder', - forcePlaceholderSize: true, - helper: function (e, ui) { - // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ - ui.children().each(function () { - $(this).width($(this).width()); - }); - return ui; - }, - start: function (e, ui) { - - var cellHeight = ui.item.height(); - - // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size - var cellCount = 0; - $('td, th', ui.helper).each(function () { - // For each td or th try and get it's colspan attribute, and add that or 1 to the total - var colspan = 1; - var colspanAttr = $(this).attr('colspan'); - if (colspanAttr > 1) { - colspan = colspanAttr; - } - cellCount += colspan; - }); - - // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr - ui.placeholder.html('').height(cellHeight); + angular + .module('umbraco') + .component('nestedContentPropertyEditor', { + templateUrl: 'views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html', + controller: NestedContentController, + controllerAs: 'vm', + require: { + umbProperty: '?^umbProperty', + umbVariantContent: '?^^umbVariantContent' } - }; + }); - - $scope.placeholder = function (config) { - return _.find($scope.model.elemTypes, function (elType) { - return elType.alias === config.ncAlias; - }); - } - - $scope.selectableElemTypesFor = function (config) { - // return all elemTypes that are: - // 1. either already selected for this config, or - // 2. not selected in any other config - return _.filter($scope.model.elemTypes, function (elType) { - return elType.alias === config.ncAlias || !_.find($scope.model.value, function (c) { - return elType.alias === c.ncAlias; - }); - }); - } - $scope.canAdd = function () { - return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) { - return !_.find($scope.model.value, function (c) { - return elType.alias === c.ncAlias; - }); - }); - } - - - $scope.openElemTypeModal = function ($event, config) { - - //we have to add the alias to the objects (they are stored as ncAlias) - var selectedItems = _.each($scope.model.value, function (obj) { - obj.alias = obj.ncAlias; - return obj; - }) - - var elemTypeSelectorOverlay = { - view: "itempicker", - title: selectElementTypeModalTitle, - availableItems: $scope.selectableElemTypesFor(config), - selectedItems: selectedItems, - position: "target", - event: $event, - submit: function (model) { - config.ncAlias = model.selectedItem.alias; - overlayService.close(); - }, - close: function () { - overlayService.close(); - } - }; - - overlayService.open(elemTypeSelectorOverlay); - } - - - - if (!$scope.model.value) { - $scope.model.value = []; - $scope.add(); - } - - } -]); - -angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [ - - "$scope", - "$interpolate", - "$filter", - "$timeout", - "contentResource", - "localizationService", - "iconHelper", - "clipboardService", - "eventsService", - "overlayService", - "$routeParams", - "editorState", - "propertyEditorService", - - function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState, propertyEditorService) { + function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState) { + var vm = this; + var model = $scope.$parent.$parent.model; + var contentTypeAliases = []; - _.each($scope.model.config.contentTypes, function (contentType) { + _.each(model.config.contentTypes, function (contentType) { contentTypeAliases.push(contentType.ncAlias); }); - _.each($scope.model.config.contentTypes, function (contentType) { + _.each(model.config.contentTypes, function (contentType) { contentType.nameExp = !!contentType.nameTemplate ? $interpolate(contentType.nameTemplate) : undefined; }); - $scope.nodes = []; - $scope.currentNode = undefined; - $scope.realCurrentNode = undefined; - $scope.scaffolds = undefined; - $scope.sorting = false; - $scope.inited = false; + vm.nodes = []; + vm.currentNode = null; + vm.scaffolds = null; + vm.sorting = false; + vm.inited = false; - $scope.minItems = $scope.model.config.minItems || 0; - $scope.maxItems = $scope.model.config.maxItems || 0; + vm.minItems = model.config.minItems || 0; + vm.maxItems = model.config.maxItems || 0; - if ($scope.maxItems === 0) - $scope.maxItems = 1000; + if (vm.maxItems === 0) + vm.maxItems = 1000; - $scope.singleMode = $scope.minItems === 1 && $scope.maxItems === 1; - $scope.showIcons = Object.toBoolean($scope.model.config.showIcons); - $scope.wideMode = Object.toBoolean($scope.model.config.hideLabel); - $scope.hasContentTypes = $scope.model.config.contentTypes.length > 0; + vm.singleMode = vm.minItems === 1 && vm.maxItems === 1; + vm.showIcons = Object.toBoolean(model.config.showIcons); + vm.wideMode = Object.toBoolean(model.config.hideLabel); + vm.hasContentTypes = model.config.contentTypes.length > 0; - $scope.labels = {}; - localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) { - $scope.labels.grid_addElement = data[0]; - $scope.labels.content_createEmpty = data[1]; + var labels = {}; + vm.labels = labels; + localizationService.localizeMany(["grid_addElement", "content_createEmpty", "actions_copy"]).then(function (data) { + labels.grid_addElement = data[0]; + labels.content_createEmpty = data[1]; + labels.copy_icon_title = data[2] }); - + function setCurrentNode(node) { + vm.currentNode = node; + updateModel(); + } var copyAllEntries = function() { syncCurrentNode(); // list aliases - var aliases = $scope.nodes.map((node) => node.contentTypeAlias); + var aliases = vm.nodes.map((node) => node.contentTypeAlias); // remove dublicates aliases = aliases.filter((item, index) => aliases.indexOf(item) === index); + + var nodeName = ""; - // Retrive variant name - var culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; - var activeVariant = _.find(editorState.current.variants, function (v) { - return !v.language || v.language.culture === culture; - }); + if(vm.umbVariantContent) { + nodeName = vm.umbVariantContent.editor.content.name; + } - localizationService.localize("clipboard_labelForArrayOfItemsFrom", [$scope.model.label, activeVariant.name]).then(function(data) { - clipboardService.copyArray("elementTypeArray", aliases, $scope.nodes, data, "icon-thumbnail-list", $scope.model.id); + localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function(data) { + clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id); }); } var copyAllEntriesAction = { labelKey: 'clipboard_labelForCopyAllEntries', - labelTokens: [$scope.model.label], + labelTokens: [model.label], icon: 'documents', method: copyAllEntries, isDisabled: true } + var removeAllEntries = function () { + localizationService.localizeMany(["content_nestedContentDeleteAllItems", "general_delete"]).then(function (data) { + overlayService.confirmDelete({ + title: data[1], + content: data[0], + close: function () { + overlayService.close(); + }, + submit: function () { + vm.nodes = []; + setDirty(); + updateModel(); + overlayService.close(); + } + }); + }); + } + + var removeAllEntriesAction = { + labelKey: 'clipboard_labelForRemoveAllEntries', + labelTokens: [], + icon: 'trash', + method: removeAllEntries, + isDisabled: true + } + + // helper to force the current form into the dirty state - $scope.setDirty = function () { - if ($scope.propertyForm) { - $scope.propertyForm.$setDirty(); + function setDirty() { + if ($scope.$parent.$parent.propertyForm) { + $scope.$parent.$parent.propertyForm.$setDirty(); } }; - $scope.addNode = function (alias) { - var scaffold = $scope.getScaffold(alias); + function addNode(alias) { + var scaffold = getScaffold(alias); var newNode = createNode(scaffold, null); - $scope.currentNode = newNode; - $scope.setDirty(); + setCurrentNode(newNode); + setDirty(); }; - $scope.openNodeTypePicker = function ($event) { - if ($scope.nodes.length >= $scope.maxItems) { + vm.openNodeTypePicker = function ($event) { + if (vm.nodes.length >= vm.maxItems) { return; } - $scope.overlayMenu = { + vm.overlayMenu = { show: false, style: {}, - filter: $scope.scaffolds.length > 12 ? true : false, + filter: vm.scaffolds.length > 12 ? true : false, orderBy: "$index", view: "itempicker", event: $event, clickPasteItem: function(item) { if (item.type === "elementTypeArray") { _.each(item.data, function (entry) { - $scope.pasteFromClipboard(entry); + pasteFromClipboard(entry); }); } else { - $scope.pasteFromClipboard(item.data); + pasteFromClipboard(item.data); } - $scope.overlayMenu.show = false; - $scope.overlayMenu = null; + vm.overlayMenu.show = false; + vm.overlayMenu = null; }, submit: function (model) { if (model && model.selectedItem) { - $scope.addNode(model.selectedItem.alias); + addNode(model.selectedItem.alias); } - $scope.overlayMenu.show = false; - $scope.overlayMenu = null; + vm.overlayMenu.show = false; + vm.overlayMenu = null; }, close: function () { - $scope.overlayMenu.show = false; - $scope.overlayMenu = null; + vm.overlayMenu.show = false; + vm.overlayMenu = null; } }; // this could be used for future limiting on node types - $scope.overlayMenu.availableItems = []; - _.each($scope.scaffolds, function (scaffold) { - $scope.overlayMenu.availableItems.push({ + vm.overlayMenu.availableItems = []; + _.each(vm.scaffolds, function (scaffold) { + vm.overlayMenu.availableItems.push({ alias: scaffold.contentTypeAlias, name: scaffold.contentTypeName, icon: iconHelper.convertFromLegacyIcon(scaffold.icon) }); }); - if ($scope.overlayMenu.availableItems.length === 0) { + if (vm.overlayMenu.availableItems.length === 0) { return; } - $scope.overlayMenu.size = $scope.overlayMenu.availableItems.length > 6 ? "medium" : "small"; + vm.overlayMenu.size = vm.overlayMenu.availableItems.length > 6 ? "medium" : "small"; - $scope.overlayMenu.pasteItems = []; + vm.overlayMenu.pasteItems = []; var singleEntriesForPaste = clipboardService.retriveEntriesOfType("elementType", contentTypeAliases); _.each(singleEntriesForPaste, function (entry) { - $scope.overlayMenu.pasteItems.push({ + vm.overlayMenu.pasteItems.push({ type: "elementType", name: entry.label, data: entry.data, @@ -326,7 +199,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop var arrayEntriesForPaste = clipboardService.retriveEntriesOfType("elementTypeArray", contentTypeAliases); _.each(arrayEntriesForPaste, function (entry) { - $scope.overlayMenu.pasteItems.push({ + vm.overlayMenu.pasteItems.push({ type: "elementTypeArray", name: entry.label, data: entry.data, @@ -334,40 +207,40 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop }); }); - $scope.overlayMenu.title = $scope.overlayMenu.pasteItems.length > 0 ? $scope.labels.grid_addElement : $scope.labels.content_createEmpty; + vm.overlayMenu.title = vm.overlayMenu.pasteItems.length > 0 ? labels.grid_addElement : labels.content_createEmpty; - $scope.overlayMenu.clickClearPaste = function ($event) { + vm.overlayMenu.clickClearPaste = function ($event) { $event.stopPropagation(); $event.preventDefault(); clipboardService.clearEntriesOfType("elementType", contentTypeAliases); clipboardService.clearEntriesOfType("elementTypeArray", contentTypeAliases); - $scope.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually. + vm.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually. }; - if ($scope.overlayMenu.availableItems.length === 1 && $scope.overlayMenu.pasteItems.length === 0) { + if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) { // only one scaffold type - no need to display the picker - $scope.addNode($scope.scaffolds[0].contentTypeAlias); + addNode(vm.scaffolds[0].contentTypeAlias); return; } - $scope.overlayMenu.show = true; + vm.overlayMenu.show = true; }; - $scope.editNode = function (idx) { - if ($scope.currentNode && $scope.currentNode.key === $scope.nodes[idx].key) { - $scope.currentNode = undefined; + vm.editNode = function (idx) { + if (vm.currentNode && vm.currentNode.key === vm.nodes[idx].key) { + setCurrentNode(null); } else { - $scope.currentNode = $scope.nodes[idx]; + setCurrentNode(vm.nodes[idx]); } }; - $scope.deleteNode = function (idx) { - $scope.nodes.splice(idx, 1); - $scope.setDirty(); + function deleteNode(idx) { + vm.nodes.splice(idx, 1); + setDirty(); updateModel(); }; - $scope.requestDeleteNode = function (idx) { - if ($scope.model.config.confirmDeletes === true) { + vm.requestDeleteNode = function (idx) { + if (model.config.confirmDeletes === true) { localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]).then(function (data) { const overlay = { title: data[1], @@ -379,7 +252,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop overlayService.close(); }, submit: function () { - $scope.deleteNode(idx); + deleteNode(idx); overlayService.close(); } }; @@ -387,23 +260,23 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop overlayService.open(overlay); }); } else { - $scope.deleteNode(idx); + deleteNode(idx); } }; - $scope.getName = function (idx) { + vm.getName = function (idx) { var name = ""; - if ($scope.model.value[idx]) { + if (model.value[idx]) { - var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias); + var contentType = getContentTypeConfig(model.value[idx].ncContentTypeAlias); if (contentType != null) { // first try getting a name using the configured label template if (contentType.nameExp) { // Run the expression against the stored dictionary value, NOT the node object - var item = $scope.model.value[idx]; + var item = model.value[idx]; // Add a temporary index property item["$index"] = (idx + 1); @@ -418,8 +291,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop } // if we still do not have a name and we have multiple content types to choose from, use the content type name (same as is shown in the content type picker) - if (!name && $scope.scaffolds.length > 1) { - var scaffold = $scope.getScaffold(contentType.ncAlias); + if (!name && vm.scaffolds.length > 1) { + var scaffold = getScaffold(contentType.ncAlias); if (scaffold) { name = scaffold.contentTypeName; } @@ -433,18 +306,19 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop } // Update the nodes actual name value - if ($scope.nodes[idx].name !== name) { - $scope.nodes[idx].name = name; + if (vm.nodes[idx].name !== name) { + vm.nodes[idx].name = name; } return name; }; - $scope.getIcon = function (idx) { - var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias); + vm.getIcon = function (idx) { + var scaffold = getScaffold(model.value[idx].ncContentTypeAlias); return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder"; } - $scope.sortableOptions = { + + vm.sortableOptions = { axis: "y", cursor: "move", handle: '.umb-nested-content__header-bar', @@ -455,46 +329,45 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop start: function (ev, ui) { updateModel(); // Yea, yea, we shouldn't modify the dom, sue me - $("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () { + $("#umb-nested-content--" + model.id + " .umb-rte textarea").each(function () { tinymce.execCommand("mceRemoveEditor", false, $(this).attr("id")); $(this).css("visibility", "hidden"); }); $scope.$apply(function () { - $scope.sorting = true; + vm.sorting = true; }); }, update: function (ev, ui) { - $scope.setDirty(); + setDirty(); }, stop: function (ev, ui) { - $("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () { + $("#umb-nested-content--" + model.id + " .umb-rte textarea").each(function () { tinymce.execCommand("mceAddEditor", true, $(this).attr("id")); $(this).css("visibility", "visible"); }); $scope.$apply(function () { - $scope.sorting = false; + vm.sorting = false; updateModel(); }); } }; - $scope.getScaffold = function (alias) { - return _.find($scope.scaffolds, function (scaffold) { + function getScaffold(alias) { + return _.find(vm.scaffolds, function (scaffold) { return scaffold.contentTypeAlias === alias; }); } - $scope.getContentTypeConfig = function (alias) { - return _.find($scope.model.config.contentTypes, function (contentType) { + function getContentTypeConfig(alias) { + return _.find(model.config.contentTypes, function (contentType) { return contentType.ncAlias === alias; }); } - $scope.showCopy = clipboardService.isSupported(); + vm.showCopy = clipboardService.isSupported(); + vm.showPaste = false; - $scope.showPaste = false; - - $scope.clickCopy = function ($event, node) { + vm.clickCopy = function ($event, node) { syncCurrentNode(); @@ -503,7 +376,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop } - $scope.pasteFromClipboard = function(newNode) { + function pasteFromClipboard(newNode) { if (newNode === undefined) { return; @@ -512,15 +385,15 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop // generate a new key. newNode.key = String.CreateGuid(); - $scope.nodes.push(newNode); - $scope.setDirty(); + vm.nodes.push(newNode); + setDirty(); //updateModel();// done by setting current node... - $scope.currentNode = newNode; + setCurrentNode(newNode); } function checkAbilityToPasteContent() { - $scope.showPaste = clipboardService.hasEntriesOfType("elementType", contentTypeAliases) || clipboardService.hasEntriesOfType("elementTypeArray", contentTypeAliases); + vm.showPaste = clipboardService.hasEntriesOfType("elementType", contentTypeAliases) || clipboardService.hasEntriesOfType("elementTypeArray", contentTypeAliases); } eventsService.on("clipboardService.storageUpdate", checkAbilityToPasteContent); @@ -533,8 +406,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop // Initialize var scaffoldsLoaded = 0; - $scope.scaffolds = []; - _.each($scope.model.config.contentTypes, function (contentType) { + vm.scaffolds = []; + _.each(model.config.contentTypes, function (contentType) { contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) { // make sure it's an element type before allowing the user to create new ones if (scaffold.isElement) { @@ -558,7 +431,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop } // Store the scaffold object - $scope.scaffolds.push(scaffold); + vm.scaffolds.push(scaffold); } scaffoldsLoaded++; @@ -571,22 +444,22 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop var initIfAllScaffoldsHaveLoaded = function () { // Initialize when all scaffolds have loaded - if ($scope.model.config.contentTypes.length === scaffoldsLoaded) { + if (model.config.contentTypes.length === scaffoldsLoaded) { // Because we're loading the scaffolds async one at a time, we need to // sort them explicitly according to the sort order defined by the data type. contentTypeAliases = []; - _.each($scope.model.config.contentTypes, function (contentType) { + _.each(model.config.contentTypes, function (contentType) { contentTypeAliases.push(contentType.ncAlias); }); - $scope.scaffolds = $filter("orderBy")($scope.scaffolds, function (s) { + vm.scaffolds = $filter("orderBy")(vm.scaffolds, function (s) { return contentTypeAliases.indexOf(s.contentTypeAlias); }); // Convert stored nodes - if ($scope.model.value) { - for (var i = 0; i < $scope.model.value.length; i++) { - var item = $scope.model.value[i]; - var scaffold = $scope.getScaffold(item.ncContentTypeAlias); + if (model.value) { + for (var i = 0; i < model.value.length; i++) { + var item = model.value[i]; + var scaffold = getScaffold(item.ncContentTypeAlias); if (scaffold == null) { // No such scaffold - the content type might have been deleted. We need to skip it. continue; @@ -596,18 +469,18 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop } // Auto-fill with elementTypes, but only if we have one type to choose from, and if this property is empty. - if ($scope.singleMode === true && $scope.nodes.length === 0 && $scope.model.config.minItems > 0) { - for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) { - $scope.addNode($scope.scaffolds[0].contentTypeAlias); + if (vm.singleMode === true && vm.nodes.length === 0 && model.config.minItems > 0) { + for (var i = vm.nodes.length; i < model.config.minItems; i++) { + addNode(vm.scaffolds[0].contentTypeAlias); } } // If there is only one item, set it as current node - if ($scope.singleMode || ($scope.nodes.length === 1 && $scope.maxItems === 1)) { - $scope.currentNode = $scope.nodes[0]; + if (vm.singleMode || (vm.nodes.length === 1 && vm.maxItems === 1)) { + setCurrentNode(vm.nodes[0]); } - $scope.inited = true; + vm.inited = true; updatePropertyActionStates(); checkAbilityToPasteContent(); @@ -628,10 +501,11 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop var prop = tab.properties[p]; prop.propertyAlias = prop.alias; - prop.alias = $scope.model.alias + "___" + prop.alias; + prop.alias = model.alias + "___" + prop.alias; // Force validation to occur server side as this is the // only way we can have consistency between mandatory and // regex validation messages. Not ideal, but it works. + prop.ncMandatory = prop.validation.mandatory; prop.validation = { mandatory: false, pattern: "" @@ -643,7 +517,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop } } - $scope.nodes.push(node); + vm.nodes.push(node); return node; } @@ -670,60 +544,61 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop function syncCurrentNode() { - if ($scope.realCurrentNode) { - $scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key }); + if (vm.currentNode) { + $scope.$broadcast("ncSyncVal", { key: vm.currentNode.key }); } } function updateModel() { syncCurrentNode(); - if ($scope.inited) { + if (vm.inited) { var newValues = []; - for (var i = 0; i < $scope.nodes.length; i++) { - newValues.push(convertNodeIntoNCEntry($scope.nodes[i])); + for (var i = 0; i < vm.nodes.length; i++) { + newValues.push(convertNodeIntoNCEntry(vm.nodes[i])); } - $scope.model.value = newValues; + model.value = newValues; } updatePropertyActionStates(); } function updatePropertyActionStates() { - copyAllEntriesAction.isDisabled = $scope.model.value.length === 0; + copyAllEntriesAction.isDisabled = !model.value || model.value.length === 0; + removeAllEntriesAction.isDisabled = !model.value || model.value.length === 0; } - $scope.$watch("currentNode", function (newVal) { - updateModel(); - $scope.realCurrentNode = newVal; - }); - var api = {}; - api.propertyActions = [ - copyAllEntriesAction + var propertyActions = [ + copyAllEntriesAction, + removeAllEntriesAction ]; - propertyEditorService.exposeAPI($scope, api);// must be executed at a state where the API is set. - + this.$onInit = function () { + if (this.umbProperty) { + this.umbProperty.setPropertyActions(propertyActions); + } + }; + var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { updateModel(); }); var watcher = $scope.$watch( function () { - return $scope.nodes.length; + return vm.nodes.length; }, function () { //Validate! - if ($scope.nodes.length < $scope.minItems) { + if (vm.nodes.length < vm.minItems) { $scope.nestedContentForm.minCount.$setValidity("minCount", false); } else { $scope.nestedContentForm.minCount.$setValidity("minCount", true); } - if ($scope.nodes.length > $scope.maxItems) { + if (vm.nodes.length > vm.maxItems) { $scope.nestedContentForm.maxCount.$setValidity("maxCount", false); } else { @@ -739,4 +614,4 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop } -]); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js new file mode 100644 index 0000000000..4a9a07428d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js @@ -0,0 +1,159 @@ +angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [ + + "$scope", + "Umbraco.PropertyEditors.NestedContent.Resources", + "overlayService", + "localizationService", + "iconHelper", + + function ($scope, ncResources, overlayService, localizationService, iconHelper) { + var selectElementTypeModalTitle = ""; + + $scope.elemTypeTabs = []; + + + init(); + + + function init() { + localizationService.localize("content_nestedContentSelectElementTypeModalTitle").then(function (value) { + selectElementTypeModalTitle = value; + }); + + ncResources.getContentTypes().then(function (elemTypes) { + $scope.model.elemTypes = elemTypes; + + // convert legacy icons + iconHelper.formatContentTypeIcons($scope.model.elemTypes); + + // Count doctype name occurrences + var elTypeNameOccurrences= _.countBy(elemTypes, 'name'); + + // Populate document type tab dictionary + // And append alias to name if multiple doctypes have the same name + elemTypes.forEach(function (value) { + $scope.elemTypeTabs[value.alias] = value.tabs; + + if (elTypeNameOccurrences[value.name] > 1) { + value.name += " (" + value.alias + ")"; + } + }); + }); + + } + + + $scope.add = function () { + $scope.model.value.push({ + // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition. + // For good measure we'll also prefix the tab alias "nc" + ncAlias: "", + ncTabAlias: "", + nameTemplate: "" + }); + } + + $scope.canAdd = function () { + return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length; + } + + $scope.remove = function (index) { + $scope.model.value.splice(index, 1); + } + + $scope.sortableOptions = { + axis: "y", + cursor: "move", + handle: ".handle", + placeholder: 'sortable-placeholder', + forcePlaceholderSize: true, + helper: function (e, ui) { + // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ + ui.children().each(function () { + $(this).width($(this).width()); + }); + return ui; + }, + start: function (e, ui) { + + var cellHeight = ui.item.height(); + + // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size + var cellCount = 0; + $('td, th', ui.helper).each(function () { + // For each td or th try and get it's colspan attribute, and add that or 1 to the total + var colspan = 1; + var colspanAttr = $(this).attr('colspan'); + if (colspanAttr > 1) { + colspan = colspanAttr; + } + cellCount += colspan; + }); + + // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr + ui.placeholder.html('').height(cellHeight); + } + }; + + + $scope.placeholder = function (config) { + return _.find($scope.model.elemTypes, function (elType) { + return elType.alias === config.ncAlias; + }); + } + + $scope.selectableElemTypesFor = function (config) { + // return all elemTypes that are: + // 1. either already selected for this config, or + // 2. not selected in any other config + return _.filter($scope.model.elemTypes, function (elType) { + return elType.alias === config.ncAlias || !_.find($scope.model.value, function (c) { + return elType.alias === c.ncAlias; + }); + }); + } + $scope.canAdd = function () { + return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) { + return !_.find($scope.model.value, function (c) { + return elType.alias === c.ncAlias; + }); + }); + } + + + $scope.openElemTypeModal = function ($event, config) { + + //we have to add the alias to the objects (they are stored as ncAlias) + var selectedItems = _.each($scope.model.value, function (obj) { + obj.alias = obj.ncAlias; + return obj; + }) + + var elemTypeSelectorOverlay = { + view: "itempicker", + title: selectElementTypeModalTitle, + availableItems: $scope.selectableElemTypesFor(config), + selectedItems: selectedItems, + position: "target", + event: $event, + submit: function (model) { + config.ncAlias = model.selectedItem.alias; + overlayService.close(); + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(elemTypeSelectorOverlay); + } + + + + if (!$scope.model.value) { + $scope.model.value = []; + $scope.add(); + } + + } +]); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html index f6bfdecb31..c6860140a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html @@ -24,12 +24,7 @@ {{ph = placeholder(config);""}}
    - - - - - {{ ph.name }} - + Add element type
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html index db0e9a016e..a2105c5675 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html @@ -1,7 +1,7 @@ 
    - + @@ -10,4 +10,4 @@

    {{property.notSupportedMessage}}

    -
    \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html index 7104f48ee9..0564c22057 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html @@ -1,73 +1,5 @@ -
    +
    - - - - -
    - -
    - -
    - -
    - - - -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    - - - - - - - - -
    -
    - Minimum %0% entries, needs %1% more. -
    -
    -
    -
    - Maximum %0% entries, %1% too many. -
    -
    - -
    - - - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html new file mode 100644 index 0000000000..d24d3796f3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html @@ -0,0 +1,75 @@ +
    + + + + + +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + +
    +
    + + + + + + + + +
    +
    + Minimum %0% entries, needs %1% more. +
    +
    +
    +
    + Maximum %0% entries, %1% too many. +
    +
    + +
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js index 0618c5e22b..2db7eaf562 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsController", - function ($scope) { + function ($scope, validationMessageService) { var vm = this; @@ -23,6 +23,12 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsContro vm.viewItems = sortedItems; } + + // Set the message to use for when a mandatory field isn't completed. + // Will either use the one provided on the property type or a localised default. + validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) { + $scope.mandatoryMessage = value; + }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html index ad006b4992..1c3aa898db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html @@ -1,7 +1,15 @@ 
    -
      -
    • - -
    • -
    + + +
      +
    • + +
    • +
    + +
    +

    {{mandatoryMessage}}

    +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js index 45df813835..884cc62d43 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js @@ -1,4 +1,4 @@ -function textAreaController($scope) { +function textAreaController($scope, validationMessageService) { // macro parameter editor doesn't contains a config object, // so we create a new one to hold any properties @@ -22,5 +22,11 @@ function textAreaController($scope) { } } $scope.model.change(); + + // Set the message to use for when a mandatory field isn't completed. + // Will either use the one provided on the property type or a localised default. + validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) { + $scope.mandatoryMessage = value; + }); } angular.module('umbraco').controller("Umbraco.PropertyEditors.textAreaController", textAreaController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index 4842a6bfb7..04bd8590d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -3,7 +3,7 @@ - Required + {{mandatoryMessage}} {{textareaFieldForm.textarea.errorMsg}} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index 32e9891eb4..e86d8caef4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -1,4 +1,4 @@ -function textboxController($scope) { +function textboxController($scope, validationMessageService) { // macro parameter editor doesn't contains a config object, // so we create a new one to hold any properties if (!$scope.model.config) { @@ -18,6 +18,11 @@ function textboxController($scope) { } } $scope.model.change(); - + + // Set the message to use for when a mandatory field isn't completed. + // Will either use the one provided on the property type or a localised default. + validationMessageService.getMandatoryMessage($scope.model.validation).then(function(value) { + $scope.mandatoryMessage = value; + }); } angular.module('umbraco').controller("Umbraco.PropertyEditors.textboxController", textboxController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index f8f9b18c7f..e1f5dac733 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -12,7 +12,7 @@

    {{model.label}} {{textboxFieldForm.textbox.errorMsg}}

    -

    Required

    +

    {{mandatoryMessage}}

    diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index e759be5e7a..ee77e4c14e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -322,7 +322,7 @@ rows="4"> - - - + + + @@ -353,9 +353,9 @@ False True - 8400 + 8600 / - http://localhost:8400/ + http://localhost:8600/ False False diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml index 79c83a883d..7034404ca3 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml @@ -9,7 +9,7 @@ Html.EnableClientValidation(); Html.EnableUnobtrusiveJavaScript(); - Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"); + Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"); Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.0/jquery.validate.min.js"); Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"); diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Login.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Login.cshtml index af84a603db..a7effa5606 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Login.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Login.cshtml @@ -11,7 +11,7 @@ Html.EnableClientValidation(); Html.EnableUnobtrusiveJavaScript(); - Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"); + Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"); Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.0/jquery.validate.min.js"); Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"); } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml index b9fbea4733..17ed95ea31 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml @@ -33,7 +33,7 @@ Html.EnableClientValidation(); Html.EnableUnobtrusiveJavaScript(); - Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"); + Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"); Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.0/jquery.validate.min.js"); Html.RequiresJs("https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"); @@ -85,7 +85,7 @@ else easily change it. For example, if you wanted to render a custom editor for this field called "MyEditor" you would create a file at ~/Views/Shared/EditorTemplates/MyEditor.cshtml", then you will change the next line of code to render your specific editor template like: - @Html.EditorFor(m => profileModel.MemberProperties[i].Value, "MyEditor") + @Html.EditorFor(m => registerModel.MemberProperties[i].Value, "MyEditor") *@ @Html.EditorFor(m => registerModel.MemberProperties[i].Value) @Html.HiddenFor(m => registerModel.MemberProperties[i].Alias) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 65f77dbf85..4b7bcaee87 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -1,72 +1,80 @@ -@using Umbraco.Core -@using ClientDependency.Core -@using ClientDependency.Core.Mvc -@using Umbraco.Core.IO -@using Umbraco.Web -@using Umbraco.Core.Configuration - -@inherits System.Web.Mvc.WebViewPage -@{ - var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant(); - - Html - .RequiresCss("assets/css/canvasdesigner.css", "Umbraco"); -} - - - - - Umbraco Preview - - - - @Html.RenderCssHere( - new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco))) - - - -
    - - @if (string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView) == false) - { - @Html.Partial(Model.PreviewExtendedHeaderView) - } - -
    - -
    -
    -
    -
    - -
    -
      -
    • - -
    • - - @if (Model.Languages != null && Model.Languages.Count() > 1) - { - foreach (var previewLink in Model.Languages) - { -
    • - @previewLink.CultureName -
    • - } - } - -
    • - -
    • -
    -
    - -
    - - - - - - +@using Umbraco.Core +@using ClientDependency.Core +@using ClientDependency.Core.Mvc +@using Umbraco.Core.IO +@using Umbraco.Web +@using Umbraco.Core.Configuration + +@inherits System.Web.Mvc.WebViewPage +@{ + var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant(); + + Html.RequiresCss("assets/css/canvasdesigner.css", "Umbraco"); +} + + + + + Umbraco Preview + + + + @Html.RenderCssHere( + new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco))) + + + +
    + + @if (string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView) == false) + { + @Html.Partial(Model.PreviewExtendedHeaderView) + } + +
    + +
    +
    + + +
    + + + + + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index cc4d996dad..0deac8b50f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -272,6 +272,7 @@ Dette oversætter til den følgende tid på serveren: Hvad betyder det?]]> Er du sikker på, at du vil slette dette element? + Er du sikker på, at du vil slette alle elementer? Egenskaben %0% anvender editoren %1% som ikke er understøttet af Nested Content. Der er ikke konfigureret nogen indholdstyper for denne egenskab. %0% fra %1% @@ -514,6 +515,10 @@ Værktøjer Værktøjer til at administrere indekset felter + Indexet skal bygges igen, for at kunne læses + Processen tager længere tid end forventet. Kontrollér Umbraco loggen for at se om der er sket fejl under operationen + Dette index kan ikke genbygess for det ikke har nogen + IIndexPopulator Indtast dit brugernavn @@ -1411,6 +1416,7 @@ Mange hilsner fra Umbraco robotten Intet fallback-sprog For at tillade flersproget indhold, som ikke er tilgængeligt i det anmodede sprog, skal du her vælge et sprog at falde tilbage på. Fallback-sprog + ingen Tilføj parameter @@ -1767,6 +1773,7 @@ Mange hilsner fra Umbraco robotten Kopier %0% %0% fra %1% + Fjern alle elementer Åben egenskabshandlinger diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index c95f059934..3f9564f8b2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -274,6 +274,7 @@ What does this mean?]]> Are you sure you want to delete this item? Property %0% uses editor %1% which is not supported by Nested Content. + Are you sure you want to delete all items? No content types are configured for this property. Add element type Select element type @@ -520,6 +521,10 @@ Tools Tools to manage the index fields + The index cannot be read and will need to be rebuilt + The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation + This index cannot be rebuilt because it has no assigned + IIndexPopulator Enter your username @@ -1635,6 +1640,7 @@ To manage your website, simply open the Umbraco back office and start adding con No fall back language To allow multi-lingual content to fall back to another language if not present in the requested language, select it here. Fall back language + none @@ -1973,7 +1979,9 @@ To manage your website, simply open the Umbraco back office and start adding con Validate as a URL ...or enter a custom validation Field is mandatory + Enter a custom validation error message (optional) Enter a regular expression + Enter a custom validation error message (optional) You need to add at least You can only have items @@ -2216,8 +2224,120 @@ To manage your website, simply open the Umbraco back office and start adding con Copy %0% %0% from %1% + Remove all items Open Property Actions + + Wait + Refresh status + Memory Cache + + + + Reload + Database Cache + + Rebuilding can be expensive. + Use it when reloading is not enough, and you think that the database cache has not been + properly generated—which would indicate some critical Umbraco issue. + ]]> + + Rebuild + Internals + + not need to use it. + ]]> + + Collect + Published Cache Status + Caches + + + Performance profiling + + + Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. +

    +

    + If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. +

    +

    + If you want the profiler to be activated by default for all page renderings, you can use the toggle below. + It will set a cookie in your browser, which then activates the profiler automatically. + In other words, the profiler will only be active by default in your browser - not everyone else's. +

    + ]]> +
    + Activate the profiler by default + Friendly reminder + + + You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. +

    + ]]> +
    + + + Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. +

    +

    + Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. +

    + ]]> +
    + + + Hours of Umbraco training videos are only a click away + + Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

    + ]]> +
    + To get you started + + + Start here + This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section + Find out more + + in the Documentation section of Our Umbraco + ]]> + + + Community Forum + ]]> + + + tutorial videos (some are free, some require a subscription) + ]]> + + + productivity boosting tools and commercial support + ]]> + + + training and certification opportunities + ]]> + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index e4cd5765c1..e4b88400e4 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -277,6 +277,7 @@ This translates to the following time on the server: What does this mean?]]> Are you sure you want to delete this item? + Are you sure you want to delete all items? Property %0% uses editor %1% which is not supported by Nested Content. No content types are configured for this property. Add element type @@ -523,6 +524,10 @@ Tools Tools to manage the index fields + The index cannot be read and will need to be rebuilt + The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation + This index cannot be rebuilt because it has no assigned + IIndexPopulator Enter your username @@ -1649,6 +1654,7 @@ To manage your website, simply open the Umbraco back office and start adding con No fall back language To allow multi-lingual content to fall back to another language if not present in the requested language, select it here. Fall back language + none Add parameter @@ -1985,7 +1991,9 @@ To manage your website, simply open the Umbraco back office and start adding con Validate as a URL ...or enter a custom validation Field is mandatory + Enter a custom validation error message (optional) Enter a regular expression + Enter a custom validation error message (optional) You need to add at least You can only have items @@ -2232,8 +2240,120 @@ To manage your website, simply open the Umbraco back office and start adding con Copy %0% %0% from %1% + Remove all items Open Property Actions + + Wait + Refresh status + Memory Cache + + + + Reload + Database Cache + + Rebuilding can be expensive. + Use it when reloading is not enough, and you think that the database cache has not been + properly generated—which would indicate some critical Umbraco issue. + ]]> + + Rebuild + Internals + + not need to use it. + ]]> + + Collect + Published Cache Status + Caches + + + Performance profiling + + + Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. +

    +

    + If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. +

    +

    + If you want the profiler to be activated by default for all page renderings, you can use the toggle below. + It will set a cookie in your browser, which then activates the profiler automatically. + In other words, the profiler will only be active by default in your browser - not everyone else's. +

    + ]]> +
    + Activate the profiler by default + Friendly reminder + + + You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. +

    + ]]> +
    + + + Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. +

    +

    + Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. +

    + ]]> +
    + + + Hours of Umbraco training videos are only a click away + + Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

    + ]]> +
    + To get you started + + + Start here + This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section + Find out more + + in the Documentation section of Our Umbraco + ]]> + + + Community Forum + ]]> + + + tutorial videos (some are free, some require a subscription) + ]]> + + + productivity boosting tools and commercial support + ]]> + + + training and certification opportunities + ]]> + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml index a7735fba79..d2a1d75ee7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml @@ -339,7 +339,7 @@ Larghezza Si Riordina - Ho finito di ordinare/key> + Ho finito di ordinare Colore di sfondo diff --git a/src/Umbraco.Web.UI/config/grid.editors.config.js b/src/Umbraco.Web.UI/config/grid.editors.config.js index 49b843689b..210d167f9e 100644 --- a/src/Umbraco.Web.UI/config/grid.editors.config.js +++ b/src/Umbraco.Web.UI/config/grid.editors.config.js @@ -7,14 +7,14 @@ }, { "name": "Image", - "nameTemplate": "{{ 'Image: ' + (value.udi | ncNodeName) }}", + "nameTemplate": "{{ value && value.udi ? (value.udi | ncNodeName) : '' }}", "alias": "media", "view": "media", "icon": "icon-picture" }, { "name": "Macro", - "nameTemplate": "{{ 'Macro: ' + value.macroAlias }}", + "nameTemplate": "{{ value && value.macroAlias ? value.macroAlias : '' }}", "alias": "macro", "view": "macro", "icon": "icon-settings-alt" @@ -27,7 +27,7 @@ }, { "name": "Headline", - "nameTemplate": "{{ 'Headline: ' + value }}", + "nameTemplate": "{{ value }}", "alias": "headline", "view": "textstring", "icon": "icon-coin", @@ -38,6 +38,7 @@ }, { "name": "Quote", + "nameTemplate": "{{ value ? value.substring(0,32) + (value.length > 32 ? '...' : '') : '' }}", "alias": "quote", "view": "textstring", "icon": "icon-quote", diff --git a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs index 4acf0c948e..0d77b35528 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs @@ -5,10 +5,10 @@ using System.Net; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; -using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Editors.Filters @@ -43,8 +43,11 @@ namespace Umbraco.Web.Editors.Filters where TModelSave: IContentSave where TModelWithProperties : IContentProperties { - protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor) + private readonly ILocalizedTextService _textService; + + protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor) { + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); } /// @@ -122,6 +125,18 @@ namespace Umbraco.Web.Editors.Filters { var properties = modelWithProperties.Properties.ToDictionary(x => x.Alias, x => x); + // Retrieve default messages used for required and regex validatation. We'll replace these + // if set with custom ones if they've been provided for a given property. + var requiredDefaultMessages = new[] + { + _textService.Localize("validation", "invalidNull"), + _textService.Localize("validation", "invalidEmpty") + }; + var formatDefaultMessages = new[] + { + _textService.Localize("validation", "invalidPattern"), + }; + foreach (var p in dto.Properties) { var editor = p.PropertyEditor; @@ -141,7 +156,7 @@ namespace Umbraco.Web.Editors.Filters var postedValue = postedProp.Value; - ValidatePropertyValue(model, modelWithProperties, editor, p, postedValue, modelState); + ValidatePropertyValue(model, modelWithProperties, editor, p, postedValue, modelState, requiredDefaultMessages, formatDefaultMessages); } @@ -157,22 +172,34 @@ namespace Umbraco.Web.Editors.Filters /// /// /// + /// + /// protected virtual void ValidatePropertyValue( TModelSave model, TModelWithProperties modelWithProperties, IDataEditor editor, ContentPropertyDto property, object postedValue, - ModelStateDictionary modelState) + ModelStateDictionary modelState, + string[] requiredDefaultMessages, + string[] formatDefaultMessages) { - // validate var valueEditor = editor.GetValueEditor(property.DataType.Configuration); foreach (var r in valueEditor.Validate(postedValue, property.IsRequired, property.ValidationRegExp)) { + // If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate(). + if (property.IsRequired && !string.IsNullOrWhiteSpace(property.IsRequiredMessage) && requiredDefaultMessages.Contains(r.ErrorMessage, StringComparer.OrdinalIgnoreCase)) + { + r.ErrorMessage = property.IsRequiredMessage; + } + + if (!string.IsNullOrWhiteSpace(property.ValidationRegExp) && !string.IsNullOrWhiteSpace(property.ValidationRegExpMessage) && formatDefaultMessages.Contains(r.ErrorMessage, StringComparer.OrdinalIgnoreCase)) + { + r.ErrorMessage = property.ValidationRegExpMessage; + } + modelState.AddPropertyError(r, property.Alias, property.Culture); } } - - } } diff --git a/src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs b/src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs index b74cd71f11..39bd6ab0f4 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs @@ -1,8 +1,6 @@ -using System.Web.Http.ModelBinding; -using Umbraco.Core; -using Umbraco.Core.Logging; +using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Editors.Filters @@ -12,7 +10,7 @@ namespace Umbraco.Web.Editors.Filters /// internal class ContentSaveModelValidator : ContentModelValidator { - public ContentSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor) + public ContentSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor, textService) { } } diff --git a/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs index 18fd334b1c..fbce2d0414 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs @@ -23,28 +23,30 @@ namespace Umbraco.Web.Editors.Filters ///
internal sealed class ContentSaveValidationAttribute : ActionFilterAttribute { - public ContentSaveValidationAttribute(): this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService) + private readonly ILogger _logger; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILocalizedTextService _textService; + private readonly IContentService _contentService; + private readonly IUserService _userService; + private readonly IEntityService _entityService; + + public ContentSaveValidationAttribute(): this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.ContentService, Current.Services.UserService, Current.Services.EntityService) { } - public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IContentService contentService, IUserService userService, IEntityService entityService) + public ContentSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IContentService contentService, IUserService userService, IEntityService entityService) { - _logger = logger; - _umbracoContextAccessor = umbracoContextAccessor; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); } - private readonly ILogger _logger; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IContentService _contentService; - private readonly IUserService _userService; - private readonly IEntityService _entityService; - public override void OnActionExecuting(HttpActionContext actionContext) { var model = (ContentItemSave)actionContext.ActionArguments["contentItem"]; - var contentItemValidator = new ContentSaveModelValidator(_logger, _umbracoContextAccessor); + var contentItemValidator = new ContentSaveModelValidator(_logger, _umbracoContextAccessor, _textService); if (!ValidateAtLeastOneVariantIsBeingSaved(model, actionContext)) return; if (!contentItemValidator.ValidateExistingContent(model, actionContext)) return; diff --git a/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs b/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs index 7351c476e4..449ef95675 100644 --- a/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs @@ -1,7 +1,6 @@ -using System.Linq; +using System; using System.Net; using System.Net.Http; -using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; @@ -21,25 +20,27 @@ namespace Umbraco.Web.Editors.Filters { private readonly ILogger _logger; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILocalizedTextService _textService; private readonly IMediaService _mediaService; private readonly IEntityService _entityService; - public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.MediaService, Current.Services.EntityService) + public MediaItemSaveValidationAttribute() : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MediaService, Current.Services.EntityService) { } - public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IMediaService mediaService, IEntityService entityService) + public MediaItemSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMediaService mediaService, IEntityService entityService) { - _logger = logger; - _umbracoContextAccessor = umbracoContextAccessor; - _mediaService = mediaService; - _entityService = entityService; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); + _mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService)); + _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); } public override void OnActionExecuting(HttpActionContext actionContext) { var model = (MediaItemSave)actionContext.ActionArguments["contentItem"]; - var contentItemValidator = new MediaSaveModelValidator(_logger, _umbracoContextAccessor); + var contentItemValidator = new MediaSaveModelValidator(_logger, _umbracoContextAccessor, _textService); if (ValidateUserAccess(model, actionContext)) { diff --git a/src/Umbraco.Web/Editors/Filters/MediaSaveModelValidator.cs b/src/Umbraco.Web/Editors/Filters/MediaSaveModelValidator.cs index 83c0885d62..87b55fea76 100644 --- a/src/Umbraco.Web/Editors/Filters/MediaSaveModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/MediaSaveModelValidator.cs @@ -1,5 +1,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Editors.Filters @@ -9,8 +10,8 @@ namespace Umbraco.Web.Editors.Filters /// internal class MediaSaveModelValidator : ContentModelValidator> { - public MediaSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor) + public MediaSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor, textService) { } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs b/src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs index 7fc2b1e648..9b47965493 100644 --- a/src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/MemberSaveModelValidator.cs @@ -21,10 +21,10 @@ namespace Umbraco.Web.Editors.Filters { private readonly IMemberTypeService _memberTypeService; - public MemberSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IMemberTypeService memberTypeService) - : base(logger, umbracoContextAccessor) + public MemberSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMemberTypeService memberTypeService) + : base(logger, umbracoContextAccessor, textService) { - _memberTypeService = memberTypeService; + _memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); } /// diff --git a/src/Umbraco.Web/Editors/Filters/MemberSaveValidationAttribute.cs b/src/Umbraco.Web/Editors/Filters/MemberSaveValidationAttribute.cs index 04b0112d56..7e2c204596 100644 --- a/src/Umbraco.Web/Editors/Filters/MemberSaveValidationAttribute.cs +++ b/src/Umbraco.Web/Editors/Filters/MemberSaveValidationAttribute.cs @@ -1,4 +1,5 @@ -using System.Web.Http.Controllers; +using System; +using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core.Logging; using Umbraco.Core.Services; @@ -14,23 +15,25 @@ namespace Umbraco.Web.Editors.Filters { private readonly ILogger _logger; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILocalizedTextService _textService; private readonly IMemberTypeService _memberTypeService; public MemberSaveValidationAttribute() - : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.MemberTypeService) + : this(Current.Logger, Current.UmbracoContextAccessor, Current.Services.TextService, Current.Services.MemberTypeService) { } - public MemberSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IMemberTypeService memberTypeService) + public MemberSaveValidationAttribute(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IMemberTypeService memberTypeService) { - _logger = logger; - _umbracoContextAccessor = umbracoContextAccessor; - _memberTypeService = memberTypeService; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); + _memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); } public override void OnActionExecuting(HttpActionContext actionContext) { var model = (MemberSave)actionContext.ActionArguments["contentItem"]; - var contentItemValidator = new MemberSaveModelValidator(_logger, _umbracoContextAccessor, _memberTypeService); + var contentItemValidator = new MemberSaveModelValidator(_logger, _umbracoContextAccessor, _textService, _memberTypeService); //now do each validation step if (contentItemValidator.ValidateExistingContent(model, actionContext)) if (contentItemValidator.ValidateProperties(model, model, actionContext)) diff --git a/src/Umbraco.Web/Editors/MacroRenderingController.cs b/src/Umbraco.Web/Editors/MacroRenderingController.cs index efad07ce89..7aabff6822 100644 --- a/src/Umbraco.Web/Editors/MacroRenderingController.cs +++ b/src/Umbraco.Web/Editors/MacroRenderingController.cs @@ -135,14 +135,19 @@ namespace Umbraco.Web.Editors // must have an active variation context! _variationContextAccessor.VariationContext = new VariationContext(culture); - var result = Request.CreateResponse(); - //need to create a specific content result formatted as HTML since this controller has been configured - //with only json formatters. - result.Content = new StringContent( - _componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(), - Encoding.UTF8, - "text/html"); - return result; + using (UmbracoContext.ForcedPreview(true)) + { + + var result = Request.CreateResponse(); + //need to create a specific content result formatted as HTML since this controller has been configured + //with only json formatters. + result.Content = new StringContent( + _componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(), + Encoding.UTF8, + "text/html"); + + return result; + } } [HttpPost] diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index e9b879c48b..c2561e334e 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -816,7 +816,7 @@ namespace Umbraco.Web.Editors } else { - throw new EntityNotFoundException(parentId, "The passed id doesn't exist"); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, "The passed id doesn't exist")); } } else diff --git a/src/Umbraco.Web/GridTemplateExtensions.cs b/src/Umbraco.Web/GridTemplateExtensions.cs index 2c6e66c68b..afa929cfbb 100644 --- a/src/Umbraco.Web/GridTemplateExtensions.cs +++ b/src/Umbraco.Web/GridTemplateExtensions.cs @@ -1,13 +1,10 @@ using System; -using System.Web.Mvc.Html; using System.Web.Mvc; -using System.IO; -using Umbraco.Core.Exceptions; +using System.Web.Mvc.Html; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web { - public static class GridTemplateExtensions { public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedProperty property, string framework = "bootstrap3") @@ -26,18 +23,20 @@ namespace Umbraco.Web public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias) { - if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); + if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias)); return html.GetGridHtml(contentItem, propertyAlias, "bootstrap3"); } public static MvcHtmlString GetGridHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias, string framework) { - if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); + if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias)); var view = "Grid/" + framework; var prop = contentItem.GetProperty(propertyAlias); - if (prop == null) throw new NullReferenceException("No property type found with alias " + propertyAlias); + if (prop == null) throw new InvalidOperationException("No property type found with alias " + propertyAlias); var model = prop.GetValue(); var asString = model as string; @@ -54,23 +53,28 @@ namespace Umbraco.Web var view = "Grid/" + framework; return html.Partial(view, property.GetValue()); } + public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html) { return GetGridHtml(contentItem, html, "bodyText", "bootstrap3"); } + public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html, string propertyAlias) { - if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); + if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias)); return GetGridHtml(contentItem, html, propertyAlias, "bootstrap3"); } + public static MvcHtmlString GetGridHtml(this IPublishedContent contentItem, HtmlHelper html, string propertyAlias, string framework) { - if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullOrEmptyException(nameof(propertyAlias)); + if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias)); var view = "Grid/" + framework; var prop = contentItem.GetProperty(propertyAlias); - if (prop == null) throw new NullReferenceException("No property type found with alias " + propertyAlias); + if (prop == null) throw new InvalidOperationException("No property type found with alias " + propertyAlias); var model = prop.GetValue(); var asString = model as string; @@ -78,12 +82,5 @@ namespace Umbraco.Web return html.Partial(view, model); } - - private class FakeView : IView - { - public void Render(ViewContext viewContext, TextWriter writer) - { - } - } } } diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 7249500441..0fd591e96b 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -8,11 +8,7 @@ using System.Web.Mvc; using System.Web.Mvc.Html; using System.Web.Routing; using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Exceptions; using Umbraco.Core.IO; -using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; @@ -169,8 +165,9 @@ namespace Umbraco.Web /// public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName, Type surfaceType) { + if (actionName == null) throw new ArgumentNullException(nameof(actionName)); + if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType)); - if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); var routeVals = new RouteValueDictionary(new {area = ""}); @@ -221,7 +218,7 @@ namespace Umbraco.Web { _viewContext = viewContext; _method = method; - _controllerName = controllerName; + _controllerName = controllerName; _encryptedString = UrlHelperRenderExtensions.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); } @@ -230,7 +227,7 @@ namespace Umbraco.Web private readonly FormMethod _method; private bool _disposed; private readonly string _encryptedString; - private readonly string _controllerName; + private readonly string _controllerName; protected override void Dispose(bool disposing) { @@ -243,12 +240,12 @@ namespace Umbraco.Web || _controllerName == "UmbProfile" || _controllerName == "UmbLoginStatus" || _controllerName == "UmbLogin") - { + { _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); - } + } //write out the hidden surface form routes - _viewContext.Writer.Write(""); + _viewContext.Writer.Write(""); base.Dispose(disposing); } @@ -355,8 +352,10 @@ namespace Umbraco.Web IDictionary htmlAttributes, FormMethod method) { - if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action)); - if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (action == null) throw new ArgumentNullException(nameof(action)); + if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action)); + if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); + if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName)); return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); } @@ -374,8 +373,10 @@ namespace Umbraco.Web object additionalRouteVals, IDictionary htmlAttributes) { - if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action)); - if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (action == null) throw new ArgumentNullException(nameof(action)); + if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action)); + if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); + if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName)); return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); } @@ -575,7 +576,9 @@ namespace Umbraco.Web IDictionary htmlAttributes, FormMethod method) { - if (string.IsNullOrWhiteSpace(action)) throw new ArgumentNullOrEmptyException(nameof(action)); + + if (action == null) throw new ArgumentNullException(nameof(action)); + if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action)); if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType)); var area = ""; @@ -687,8 +690,10 @@ namespace Umbraco.Web IDictionary htmlAttributes, FormMethod method) { - if (string.IsNullOrEmpty(action)) throw new ArgumentNullOrEmptyException(nameof(action)); - if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (action == null) throw new ArgumentNullException(nameof(action)); + if (string.IsNullOrEmpty(action)) throw new ArgumentException("Value can't be empty.", nameof(action)); + if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); + if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName)); var formAction = Current.UmbracoContext.OriginalRequestUrl.PathAndQuery; return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals); diff --git a/src/Umbraco.Web/HttpUrlHelperExtensions.cs b/src/Umbraco.Web/HttpUrlHelperExtensions.cs index 2eb45465cf..4e5533d327 100644 --- a/src/Umbraco.Web/HttpUrlHelperExtensions.cs +++ b/src/Umbraco.Web/HttpUrlHelperExtensions.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Linq.Expressions; using System.Web.Http.Routing; using Umbraco.Core; -using Umbraco.Core.Exceptions; using Umbraco.Web.Composing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -53,7 +52,8 @@ namespace Umbraco.Web /// public static string GetUmbracoApiService(this UrlHelper url, string actionName, Type apiControllerType, object id = null) { - if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); + if (actionName == null) throw new ArgumentNullException(nameof(actionName)); + if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); if (apiControllerType == null) throw new ArgumentNullException(nameof(apiControllerType)); var area = ""; @@ -95,8 +95,10 @@ namespace Umbraco.Web /// public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, string area, object id = null) { - if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); - if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (actionName == null) throw new ArgumentNullException(nameof(actionName)); + if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName)); + if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); + if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(controllerName)); string routeName; if (area.IsNullOrWhiteSpace()) diff --git a/src/Umbraco.Web/Install/InstallException.cs b/src/Umbraco.Web/Install/InstallException.cs index a9f254e921..3dc297e6b2 100644 --- a/src/Umbraco.Web/Install/InstallException.cs +++ b/src/Umbraco.Web/Install/InstallException.cs @@ -1,44 +1,106 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.Serialization; namespace Umbraco.Web.Install { /// - /// Used for steps to be able to return a json structure back to the UI + /// Used for steps to be able to return a JSON structure back to the UI. /// - internal class InstallException : Exception + /// + [Serializable] + public class InstallException : Exception { - private readonly string _message; + /// + /// Gets the view. + /// + /// + /// The view. + /// public string View { get; private set; } + + /// + /// Gets the view model. + /// + /// + /// The view model. + /// + /// + /// This object is not included when serializing. + /// public object ViewModel { get; private set; } - public override string Message - { - get { return _message; } - } + /// + /// Initializes a new instance of the class. + /// + public InstallException() + { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public InstallException(string message) + : this(message, "error", null) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The view model. + public InstallException(string message, object viewModel) + : this(message, "error", viewModel) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The view. + /// The view model. public InstallException(string message, string view, object viewModel) + : base(message) { - _message = message; View = view; ViewModel = viewModel; } - public InstallException(string message, object viewModel) + /// + /// 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 InstallException(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 InstallException(SerializationInfo info, StreamingContext context) + : base(info, context) { - _message = message; - View = "error"; - ViewModel = viewModel; + View = info.GetString(nameof(View)); } - public InstallException(string message) + /// + /// 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) { - _message = message; - View = "error"; - ViewModel = null; + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + + info.AddValue(nameof(View), View); + + base.GetObjectData(info, context); } } } diff --git a/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs b/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs index 4d64d30e9d..c273d642ed 100644 --- a/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs +++ b/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs @@ -39,7 +39,8 @@ namespace Umbraco.Web.Install.InstallSteps var fileName = IOHelper.MapPath($"{SystemDirectories.Root}/web.config"); var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace); - var systemWeb = xml.Root.DescendantsAndSelf("system.web").Single(); + // we only want to get the element that is under the root, (there may be more under tags we don't want them) + var systemWeb = xml.Root.Element("system.web"); // Update appSetting if it exists, or else create a new appSetting for the given key and value var machineKey = systemWeb.Descendants("machineKey").FirstOrDefault(); diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 3368def084..8d7d3bc013 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -186,7 +186,7 @@ namespace Umbraco.Web.Macros foreach (var prop in model.Properties) { var key = prop.Key.ToLowerInvariant(); - prop.Value = macroParams.ContainsKey(key) + prop.Value = macroParams != null && macroParams.ContainsKey(key) ? macroParams[key]?.ToString() ?? string.Empty : string.Empty; } diff --git a/src/Umbraco.Web/Media/Exif/ExifExceptions.cs b/src/Umbraco.Web/Media/Exif/ExifExceptions.cs index 040e84ff99..3d2ce61e9e 100644 --- a/src/Umbraco.Web/Media/Exif/ExifExceptions.cs +++ b/src/Umbraco.Web/Media/Exif/ExifExceptions.cs @@ -1,42 +1,47 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Web.Media.Exif { /// - /// The exception that is thrown when the format of the JPEG/Exif file - /// could not be understood. + /// The exception that is thrown when the format of the JPEG/EXIF file could not be understood. /// - internal class NotValidExifFileException : Exception + /// + [Serializable] + public class NotValidExifFileException : Exception { + /// + /// Initializes a new instance of the class. + /// public NotValidExifFileException() - : base("Not a valid JPEG/Exif file.") - { - ; - } + : base("Not a valid JPEG/EXIF file.") + { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. public NotValidExifFileException(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 NotValidExifFileException(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 NotValidExifFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } - /// - /// The exception that is thrown when an invalid enum type is given to an - /// ExifEnumProperty. - /// - internal class UnknownEnumTypeException : Exception - { - public UnknownEnumTypeException() - : base("Unknown enum type.") - { - ; - } - - public UnknownEnumTypeException(string message) - : base(message) - { - ; - } - } } diff --git a/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs b/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs index 40ba275fe4..63c1ce3365 100644 --- a/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs +++ b/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web.Media.Exif return new ExifInterOperability(tagid, 3, 1, ExifBitConverter.GetBytes((ushort)((object)mValue), BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); } else - throw new UnknownEnumTypeException(); + throw new InvalidOperationException($"An invalid enum type ({basetype.FullName}) was provided for type {type.FullName}"); } } } diff --git a/src/Umbraco.Web/Media/Exif/JPEGExceptions.cs b/src/Umbraco.Web/Media/Exif/JPEGExceptions.cs index 631c3cb1b9..40fd9f3be8 100644 --- a/src/Umbraco.Web/Media/Exif/JPEGExceptions.cs +++ b/src/Umbraco.Web/Media/Exif/JPEGExceptions.cs @@ -1,99 +1,171 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Web.Media.Exif { - /// - /// The exception that is thrown when the format of the image file - /// could not be understood. - /// - internal class NotValidImageFileException : Exception - { - public NotValidImageFileException() - : base("Not a valid image file.") - { - ; - } - - public NotValidImageFileException(string message) - : base(message) - { - ; - } - } /// - /// The exception that is thrown when the format of the JPEG file - /// could not be understood. + /// The exception that is thrown when the format of the JPEG file could not be understood. /// - internal class NotValidJPEGFileException : Exception + /// + [Serializable] + public class NotValidJPEGFileException : Exception { + /// + /// Initializes a new instance of the class. + /// public NotValidJPEGFileException() : base("Not a valid JPEG file.") - { - ; - } + { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. public NotValidJPEGFileException(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 NotValidJPEGFileException(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 NotValidJPEGFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } /// - /// The exception that is thrown when the format of the TIFF file - /// could not be understood. + /// The exception that is thrown when the format of the TIFF file could not be understood. /// - internal class NotValidTIFFileException : Exception + /// + [Serializable] + public class NotValidTIFFileException : Exception { + /// + /// Initializes a new instance of the class. + /// public NotValidTIFFileException() : base("Not a valid TIFF file.") - { - ; - } + { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. public NotValidTIFFileException(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 NotValidTIFFileException(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 NotValidTIFFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } /// - /// The exception that is thrown when the format of the TIFF header - /// could not be understood. + /// The exception that is thrown when the format of the TIFF header could not be understood. /// + /// + [Serializable] internal class NotValidTIFFHeader : Exception { + /// + /// Initializes a new instance of the class. + /// public NotValidTIFFHeader() : base("Not a valid TIFF header.") - { - ; - } + { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. public NotValidTIFFHeader(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 NotValidTIFFHeader(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 NotValidTIFFHeader(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } /// /// The exception that is thrown when the length of a section exceeds 64 kB. /// - internal class SectionExceeds64KBException : Exception + /// + [Serializable] + public class SectionExceeds64KBException : Exception { + /// + /// Initializes a new instance of the class. + /// public SectionExceeds64KBException() : base("Section length exceeds 64 kB.") - { - ; - } + { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. public SectionExceeds64KBException(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 SectionExceeds64KBException(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 SectionExceeds64KBException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs index 1f9d0e9b77..83e5e2a9b2 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs @@ -11,10 +11,17 @@ namespace Umbraco.Web.Models.ContentEditing internal class ContentPropertyDto : ContentPropertyBasic { public IDataType DataType { get; set; } + public string Label { get; set; } + public string Description { get; set; } + public bool IsRequired { get; set; } + + public string IsRequiredMessage { get; set; } + public string ValidationRegExp { get; set; } + public string ValidationRegExpMessage { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs index 929c7ce324..a45af12341 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs @@ -11,7 +11,13 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "mandatory")] public bool Mandatory { get; set; } + [DataMember(Name = "mandatoryMessage")] + public string MandatoryMessage { get; set; } + [DataMember(Name = "pattern")] public string Pattern { get; set; } + + [DataMember(Name = "patternMessage")] + public string PatternMessage { get; set; } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index f68c5d8b44..d81f81abe2 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -42,7 +42,9 @@ namespace Umbraco.Web.Models.Mapping //add the validation information dest.Validation.Mandatory = originalProp.PropertyType.Mandatory; + dest.Validation.MandatoryMessage = originalProp.PropertyType.MandatoryMessage; dest.Validation.Pattern = originalProp.PropertyType.ValidationRegExp; + dest.Validation.PatternMessage = originalProp.PropertyType.ValidationRegExpMessage; if (dest.PropertyEditor == null) { diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs index 72107c6201..fd57e4dd2e 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs @@ -21,7 +21,9 @@ namespace Umbraco.Web.Models.Mapping base.Map(property, dest, context); dest.IsRequired = property.PropertyType.Mandatory; + dest.IsRequiredMessage = property.PropertyType.MandatoryMessage; dest.ValidationRegExp = property.PropertyType.ValidationRegExp; + dest.ValidationRegExpMessage = property.PropertyType.ValidationRegExpMessage; dest.Description = property.PropertyType.Description; dest.Label = property.PropertyType.Name; dest.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId); diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index 528d5f6de5..95101da4e3 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -222,9 +222,13 @@ namespace Umbraco.Web.Models.Mapping target.DataTypeId = source.DataTypeId; target.DataTypeKey = source.DataTypeKey; target.Mandatory = source.Validation.Mandatory; + target.MandatoryMessage = source.Validation.MandatoryMessage; target.ValidationRegExp = source.Validation.Pattern; - target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing; - + target.ValidationRegExpMessage = source.Validation.PatternMessage; + target.Variations = source.AllowCultureVariant + ? target.Variations.SetFlag(ContentVariation.Culture) + : target.Variations.UnsetFlag(ContentVariation.Culture); + if (source.Id > 0) target.Id = source.Id; @@ -395,9 +399,9 @@ namespace Umbraco.Web.Models.Mapping if (!(target is IMemberType)) { - target.Variations = ContentVariation.Nothing; - if (source.AllowCultureVariant) - target.Variations |= ContentVariation.Culture; + target.Variations = source.AllowCultureVariant + ? target.Variations.SetFlag(ContentVariation.Culture) + : target.Variations.UnsetFlag(ContentVariation.Culture); } // handle property groups and property types diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index afd1a6b61c..34b8f664f3 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -177,6 +177,14 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Values.ContainsKey("nodeName") ? source.Values["nodeName"] : "[no name]"; + if (source.Values.TryGetValue(UmbracoExamineIndex.UmbracoFileFieldName, out var umbracoFile)) + { + if (umbracoFile != null) + { + target.Name = $"{target.Name} ({umbracoFile})"; + } + } + if (source.Values.ContainsKey(UmbracoExamineIndex.NodeKeyFieldName)) { if (Guid.TryParse(source.Values[UmbracoExamineIndex.NodeKeyFieldName], out var key)) diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs index a184ac92cf..2f5822d1e3 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs @@ -223,7 +223,13 @@ namespace Umbraco.Web.Models.Mapping Alias = p.Alias, Description = p.Description, Editor = p.PropertyEditorAlias, - Validation = new PropertyTypeValidation {Mandatory = p.Mandatory, Pattern = p.ValidationRegExp}, + Validation = new PropertyTypeValidation + { + Mandatory = p.Mandatory, + MandatoryMessage = p.MandatoryMessage, + Pattern = p.ValidationRegExp, + PatternMessage = p.ValidationRegExpMessage, + }, Label = p.Name, View = propertyEditor.GetValueEditor().View, Config = config, diff --git a/src/Umbraco.Web/Models/Trees/TreeNode.cs b/src/Umbraco.Web/Models/Trees/TreeNode.cs index 63614b141f..dc383bd18b 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNode.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNode.cs @@ -1,10 +1,9 @@ -using System.Runtime.Serialization; -using Umbraco.Core.IO; +using System; using System.Collections.Generic; +using System.Runtime.Serialization; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; -using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Trees @@ -27,7 +26,8 @@ namespace Umbraco.Web.Models.Trees /// internal TreeNode(string nodeId, string parentId, string getChildNodesUrl, string menuUrl) { - if (string.IsNullOrWhiteSpace(nodeId)) throw new ArgumentNullOrEmptyException(nameof(nodeId)); + if (nodeId == null) throw new ArgumentNullException(nameof(nodeId)); + if (string.IsNullOrWhiteSpace(nodeId)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(nodeId)); Id = nodeId; ParentId = parentId; diff --git a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs index 61a659615f..493d9deed2 100644 --- a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs @@ -6,7 +6,6 @@ using System.Web.Routing; using System.Web.SessionState; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Exceptions; using Umbraco.Web.WebApi; namespace Umbraco.Web.Mvc @@ -48,7 +47,8 @@ namespace Umbraco.Web.Mvc bool isMvc = true, string areaPathPrefix = "") { - if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentException("Value can't be empty.", nameof(controllerName)); if (controllerSuffixName == null) throw new ArgumentNullException(nameof(controllerSuffixName)); if (controllerType == null) throw new ArgumentNullException(nameof(controllerType)); diff --git a/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs index b08fde081a..db2040665c 100644 --- a/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs +++ b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs @@ -1,5 +1,4 @@ using System; -using System.Net; using System.Runtime.Serialization; using System.Web; @@ -12,6 +11,13 @@ namespace Umbraco.Web.Mvc [Serializable] public sealed class HttpUmbracoFormRouteStringException : HttpException { + /// + /// Initializes a new instance of the class. + /// + public HttpUmbracoFormRouteStringException() + { } + + /// /// Initializes a new instance of the class. /// /// The that holds the serialized object data about the exception being thrown. @@ -36,6 +42,5 @@ namespace Umbraco.Web.Mvc public HttpUmbracoFormRouteStringException(string message, Exception innerException) : base(message, innerException) { } - } } diff --git a/src/Umbraco.Web/Mvc/ModelBindingException.cs b/src/Umbraco.Web/Mvc/ModelBindingException.cs index d675ae4a65..548b548d4e 100644 --- a/src/Umbraco.Web/Mvc/ModelBindingException.cs +++ b/src/Umbraco.Web/Mvc/ModelBindingException.cs @@ -1,14 +1,45 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Web.Mvc { + /// + /// The exception that is thrown when an error occurs while binding a source to a model. + /// + /// + [Serializable] public class ModelBindingException : Exception { + /// + /// Initializes a new instance of the class. + /// public ModelBindingException() { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. public ModelBindingException(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 ModelBindingException(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 ModelBindingException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } } diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 702408788a..052af18aa1 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors "fileupload", Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-download-alt")] - public class FileUploadPropertyEditor : DataEditor + public class FileUploadPropertyEditor : DataEditor, IDataEditorWithMediaPath { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSection; @@ -43,6 +43,8 @@ namespace Umbraco.Web.PropertyEditors return editor; } + public string GetMediaPath(object value) => value?.ToString(); + /// /// Gets a value indicating whether a property is an upload field. /// @@ -52,7 +54,7 @@ namespace Umbraco.Web.PropertyEditors { return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField; } - + /// /// Ensures any files associated are removed /// diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 9a8fb7c40b..b0e5bf30bd 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.PropertyEditors HideLabel = false, Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-crop")] - public class ImageCropperPropertyEditor : DataEditor + public class ImageCropperPropertyEditor : DataEditor, IDataEditorWithMediaPath { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSettings; @@ -48,6 +48,8 @@ namespace Umbraco.Web.PropertyEditors _autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, _contentSettings); } + public string GetMediaPath(object value) => GetFileSrcFromPropertyValue(value, out _, false); + /// /// Creates the corresponding property value editor. /// @@ -63,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors /// /// Gets a value indicating whether a property is an image cropper field. /// - /// The property. + /// The property. /// A value indicating whether a property is an image cropper field, and (optionally) has a non-empty value. private static bool IsCropperField(Property property) { @@ -132,8 +134,9 @@ namespace Umbraco.Web.PropertyEditors /// /// /// The deserialized value + /// Should the path returned be the application relative path /// - private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue) + private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue, bool relative = true) { deserializedValue = null; if (propVal == null || !(propVal is string str)) return null; @@ -141,7 +144,7 @@ namespace Umbraco.Web.PropertyEditors deserializedValue = GetJObject(str, true); if (deserializedValue?["src"] == null) return null; var src = deserializedValue["src"].Value(); - return _mediaFileSystem.GetRelativePath(src); + return relative ? _mediaFileSystem.GetRelativePath(src) : src; } /// diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs index b904a2250d..0f53207462 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs @@ -8,22 +8,22 @@ namespace Umbraco.Web.PropertyEditors /// public class NestedContentConfiguration { - [ConfigurationField("contentTypes", "Document types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the document types to use as the item blueprints. Only \"element\" types can be used.")] + [ConfigurationField("contentTypes", "Element Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the Element Types to use as models for the items.")] public ContentType[] ContentTypes { get; set; } - [ConfigurationField("minItems", "Min Items", "number", Description = "Set the minimum number of items allowed.")] + [ConfigurationField("minItems", "Min Items", "number", Description = "Minimum number of items allowed.")] public int? MinItems { get; set; } - [ConfigurationField("maxItems", "Max Items", "number", Description = "Set the maximum number of items allowed.")] + [ConfigurationField("maxItems", "Max Items", "number", Description = "Maximum number of items allowed.")] public int? MaxItems { get; set; } - [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Set whether item deletions should require confirming.")] + [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Requires editor confirmation for delete actions.")] public bool ConfirmDeletes { get; set; } = true; - [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Set whether to show the items doc type icon in the list.")] + [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Show the Element Type icons.")] public bool ShowIcons { get; set; } = true; - [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Set whether to hide the editor label and have the list take up the full width of the editor window.")] + [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Hide the property label and let the item list span the full width of the editor window.")] public bool HideLabel { get; set; } public class ContentType diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 64bffc7a49..550fd507d5 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -235,11 +235,24 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new WriteLockInfo(); try { - Lock(lockInfo); + try + { + // Trying to lock could throw exceptions so always make sure to clean up. + Lock(lockInfo); + } + finally + { + try + { + _localDb?.Dispose(); + } + catch { /* TBD: May already be throwing so don't throw again */} + finally + { + _localDb = null; + } + } - if (_localDb == null) return; - _localDb.Dispose(); - _localDb = null; } finally { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 80662f5db0..a866297d72 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -57,7 +57,8 @@ namespace Umbraco.Web.PublishedCache.NuCache private BPlusTree _localContentDb; private BPlusTree _localMediaDb; - private bool _localDbExists; + private bool _localContentDbExists; + private bool _localMediaDbExists; // define constant - determines whether to use cache when previewing // to store eg routes, property converted values, anything - caching @@ -127,9 +128,9 @@ namespace Umbraco.Web.PublishedCache.NuCache // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to // figure out whether it can read the databases or it should populate them from sql - _logger.Info("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localDbExists); + _logger.Info("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localContentDbExists); _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localContentDb); - _logger.Info("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localDbExists); + _logger.Info("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localMediaDbExists); _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localMediaDb); } else @@ -170,14 +171,15 @@ namespace Umbraco.Web.PublishedCache.NuCache var path = GetLocalFilesPath(); var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - var localContentDbExists = File.Exists(localContentDbPath); - var localMediaDbExists = File.Exists(localMediaDbPath); - _localDbExists = localContentDbExists && localMediaDbExists; - // if both local databases exist then GetTree will open them, else new databases will be created - _localContentDb = BTree.GetTree(localContentDbPath, _localDbExists); - _localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists); + + _localContentDbExists = File.Exists(localContentDbPath); + _localMediaDbExists = File.Exists(localMediaDbPath); - _logger.Info("Registered with MainDom, localContentDbExists? {LocalContentDbExists}, localMediaDbExists? {LocalMediaDbExists}", localContentDbExists, localMediaDbExists); + // if both local databases exist then GetTree will open them, else new databases will be created + _localContentDb = BTree.GetTree(localContentDbPath, _localContentDbExists); + _localMediaDb = BTree.GetTree(localMediaDbPath, _localMediaDbExists); + + _logger.Info("Registered with MainDom, localContentDbExists? {LocalContentDbExists}, localMediaDbExists? {LocalMediaDbExists}", _localContentDbExists, _localMediaDbExists); } /// @@ -210,11 +212,15 @@ namespace Umbraco.Web.PublishedCache.NuCache try { - if (_localDbExists) + if (_localContentDbExists) { okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); if (!okContent) - _logger.Warn("Loading content from local db raised warnings, will reload from database."); + _logger.Warn("Loading content from local db raised warnings, will reload from database."); + } + + if (_localMediaDbExists) + { okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true)); if (!okMedia) _logger.Warn("Loading media from local db raised warnings, will reload from database."); diff --git a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs index 02dc4ebf29..89abde0576 100644 --- a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs @@ -1,7 +1,7 @@ using System; -using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.Routing { @@ -10,13 +10,26 @@ namespace Umbraco.Web.Routing /// public class DefaultMediaUrlProvider : IMediaUrlProvider { + private readonly PropertyEditorCollection _propertyEditors; + + public DefaultMediaUrlProvider(PropertyEditorCollection propertyEditors) + { + _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + } + + [Obsolete("Use the constructor with all parameters instead")] + public DefaultMediaUrlProvider() : this(Current.PropertyEditors) + { + } + /// public virtual UrlInfo GetMediaUrl(UmbracoContext umbracoContext, IPublishedContent content, - string propertyAlias, - UrlMode mode, string culture, Uri current) + string propertyAlias, UrlMode mode, string culture, Uri current) { var prop = content.GetProperty(propertyAlias); - var value = prop?.GetValue(culture); + + // get the raw source value since this is what is used by IDataEditorWithMediaPath for processing + var value = prop?.GetSourceValue(culture); if (value == null) { return null; @@ -25,15 +38,10 @@ namespace Umbraco.Web.Routing var propType = prop.PropertyType; string path = null; - switch (propType.EditorAlias) + if (_propertyEditors.TryGet(propType.EditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) { - case Constants.PropertyEditors.Aliases.UploadField: - path = value.ToString(); - break; - case Constants.PropertyEditors.Aliases.ImageCropper: - //get the url from the json format - path = value is ImageCropperValue stronglyTyped ? stronglyTyped.Src : value.ToString(); - break; + path = dataEditor.GetMediaPath(value); } var url = AssembleUrl(path, current, mode); diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 13cd717fd1..ffcd2343ed 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -1,4 +1,6 @@ -using System.Web; +using System; +using System.Web; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; @@ -17,11 +19,18 @@ namespace Umbraco.Web.Runtime private readonly UmbracoApplicationBase _umbracoApplication; private IProfiler _webProfiler; + [Obsolete("Use the ctor with all parameters instead")] + public WebRuntime(UmbracoApplicationBase umbracoApplication) + : this(umbracoApplication, null, null) + { + } + /// /// Initializes a new instance of the class. /// /// - public WebRuntime(UmbracoApplicationBase umbracoApplication) + public WebRuntime(UmbracoApplicationBase umbracoApplication, ILogger logger, IMainDom mainDom) + : base(logger, mainDom) { _umbracoApplication = umbracoApplication; } diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index 47e97593f0..c0475b1f79 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -199,7 +199,7 @@ namespace Umbraco.Web.Scheduling { lock (_locker) { - var task = _runningTask ?? Task.FromResult(0); + var task = _runningTask ?? Task.CompletedTask; return new ThreadingTaskImmutable(task); } } @@ -211,8 +211,9 @@ namespace Umbraco.Web.Scheduling /// An awaitable object. /// /// Used to wait until the runner has terminated. - /// This is for unit tests and should not be used otherwise. In most cases when the runner - /// has terminated, the application domain is going down and it is not the right time to do things. + /// + /// The only time the runner will be terminated is by the Hosting Environment when the application is being shutdown. + /// /// internal ThreadingTaskImmutable TerminatedAwaitable { @@ -338,29 +339,37 @@ namespace Umbraco.Web.Scheduling if (_isRunning == false) return; // done already } + var hasTasks = TaskCount > 0; + + if (!force && hasTasks) + _logger.Info("{LogPrefix} Waiting for tasks to complete", _logPrefix); + // complete the queue // will stop waiting on the queue or on a latch _tasks.Complete(); if (force) { - // we must bring everything down, now - Thread.Sleep(100); // give time to Complete() + // we must bring everything down, now lock (_locker) { // was Complete() enough? - if (_isRunning == false) return; + // if _tasks.Complete() ended up triggering code to stop the runner and reset + // the _isRunning flag, then there's no need to initiate a cancel on the cancelation token. + if (_isRunning == false) + return; } + // try to cancel running async tasks (cannot do much about sync tasks) // break latched tasks // stop processing the queue - _shutdownTokenSource.Cancel(false); // false is the default - _shutdownTokenSource.Dispose(); + _shutdownTokenSource?.Cancel(false); // false is the default + _shutdownTokenSource?.Dispose(); _shutdownTokenSource = null; } // tasks in the queue will be executed... - if (wait == false) return; + if (!wait) return; _runningTask?.Wait(CancellationToken.None); // wait for whatever is running to end... } @@ -428,7 +437,7 @@ namespace Umbraco.Web.Scheduling lock (_locker) { // deal with race condition - if (_shutdownToken.IsCancellationRequested == false && _tasks.Count > 0) continue; + if (_shutdownToken.IsCancellationRequested == false && TaskCount > 0) continue; // if we really have nothing to do, stop _logger.Debug("{LogPrefix} Stopping", _logPrefix); @@ -453,7 +462,7 @@ namespace Umbraco.Web.Scheduling // if KeepAlive is false then don't block, exit if there is // no task in the buffer - yes, there is a race condition, which // we'll take care of - if (_options.KeepAlive == false && _tasks.Count == 0) + if (_options.KeepAlive == false && TaskCount == 0) return null; try @@ -503,15 +512,19 @@ namespace Umbraco.Web.Scheduling // returns the task that completed // - latched.Latch completes when the latch releases // - _tasks.Completion completes when the runner completes - // - tokenTaskSource.Task completes when this task, or the whole runner, is cancelled + // - tokenTaskSource.Task completes when this task, or the whole runner is cancelled var task = await Task.WhenAny(latched.Latch, _tasks.Completion, tokenTaskSource.Task); // ok to run now if (task == latched.Latch) return bgTask; + // we are shutting down if the _tasks.Complete(); was called or the shutdown token was cancelled + var isShuttingDown = _shutdownToken.IsCancellationRequested || task == _tasks.Completion; + // if shutting down, return the task only if it runs on shutdown - if (_shutdownToken.IsCancellationRequested == false && latched.RunsOnShutdown) return bgTask; + if (isShuttingDown && latched.RunsOnShutdown) + return bgTask; // else, either it does not run on shutdown or it's been cancelled, dispose latched.Dispose(); @@ -578,17 +591,18 @@ namespace Umbraco.Web.Scheduling // triggers when the hosting environment requests that the runner terminates internal event TypedEventHandler, EventArgs> Terminating; - // triggers when the runner has terminated (no task can be added, no task is running) + // triggers when the hosting environment has terminated (no task can be added, no task is running) internal event TypedEventHandler, EventArgs> Terminated; private void OnEvent(TypedEventHandler, EventArgs> handler, string name) { - if (handler == null) return; OnEvent(handler, name, EventArgs.Empty); } private void OnEvent(TypedEventHandler, TArgs> handler, string name, TArgs e) { + _logger.Debug("{LogPrefix} OnEvent {EventName}", _logPrefix, name); + if (handler == null) return; try @@ -664,17 +678,16 @@ namespace Umbraco.Web.Scheduling #endregion + #region IRegisteredObject.Stop + /// - /// Requests a registered object to un-register. + /// Used by IRegisteredObject.Stop and shutdown on threadpool threads to not block shutdown times. /// - /// true to indicate the registered object should un-register from the hosting - /// environment before returning; otherwise, false. - /// - /// "When the application manager needs to stop a registered object, it will call the Stop method." - /// The application manager will call the Stop method to ask a registered object to un-register. During - /// processing of the Stop method, the registered object must call the HostingEnvironment.UnregisterObject method. - /// - public void Stop(bool immediate) + /// + /// + /// An awaitable Task that is used to handle the shutdown. + /// + internal Task StopInternal(bool immediate) { // the first time the hosting environment requests that the runner terminates, // raise the Terminating event - that could be used to prevent any process that @@ -693,33 +706,90 @@ namespace Umbraco.Web.Scheduling if (onTerminating) OnEvent(Terminating, "Terminating"); - if (immediate == false) + // Run the Stop commands on another thread since IRegisteredObject.Stop calls are called sequentially + // with a single aspnet thread during shutdown and we don't want to delay other calls to IRegisteredObject.Stop. + if (!immediate) { - // The Stop method is first called with the immediate parameter set to false. The object can either complete - // processing, call the UnregisterObject method, and then return or it can return immediately and complete - // processing asynchronously before calling the UnregisterObject method. + return Task.Run(StopInitial, CancellationToken.None); + } + else + { + lock (_locker) + { + if (_terminated) return Task.CompletedTask; + return Task.Run(StopImmediate, CancellationToken.None); + } + } + } - _logger.Info("{LogPrefix} Waiting for tasks to complete", _logPrefix); + /// + /// Requests a registered object to un-register. + /// + /// true to indicate the registered object should un-register from the hosting + /// environment before returning; otherwise, false. + /// + /// "When the application manager needs to stop a registered object, it will call the Stop method." + /// The application manager will call the Stop method to ask a registered object to un-register. During + /// processing of the Stop method, the registered object must call the HostingEnvironment.UnregisterObject method. + /// + public void Stop(bool immediate) => StopInternal(immediate); + + /// + /// Called when immediate == false for IRegisteredObject.Stop(bool immediate) + /// + /// + /// Called on a threadpool thread + /// + private void StopInitial() + { + // immediate == false when the app is trying to wind down, immediate == true will be called either: + // after a call with immediate == false or if the app is not trying to wind down and needs to immediately stop. + // So Stop may be called twice or sometimes only once. + + try + { Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait - + } + finally + { // raise the completed event only after the running threading task has completed lock (_locker) { if (_runningTask != null) - _runningTask.ContinueWith(_ => Terminate(false)); + _runningTask.ContinueWith(_ => StopImmediate()); else - Terminate(false); + StopImmediate(); } } - else - { - // If the registered object does not complete processing before the application manager's time-out - // period expires, the Stop method is called again with the immediate parameter set to true. When the - // immediate parameter is true, the registered object must call the UnregisterObject method before returning; - // otherwise, its registration will be removed by the application manager. - _logger.Info("{LogPrefix} Canceling tasks", _logPrefix); + // If the shutdown token was not canceled in the Shutdown call above, it means there was still tasks + // being processed, in which case we'll give it a couple seconds + if (!_shutdownToken.IsCancellationRequested) + { + // If we are called with immediate == false, wind down above and then shutdown within 2 seconds, + // we want to shut down the app as quick as possible, if we wait until immediate == true, this can + // take a very long time since immediate will only be true when a new request is received on the new + // appdomain (or another iis timeout occurs ... which can take some time). + Thread.Sleep(2000); //we are already on a threadpool thread + StopImmediate(); + } + } + + /// + /// Called when immediate == true for IRegisteredObject.Stop(bool immediate) + /// + /// + /// Called on a threadpool thread + /// + private void StopImmediate() + { + _logger.Info("{LogPrefix} Canceling tasks", _logPrefix); + try + { Shutdown(true, true); // cancel all tasks, wait for the current one to end + } + finally + { Terminate(true); } } @@ -732,7 +802,13 @@ namespace Umbraco.Web.Scheduling // raise the Terminated event // complete the awaitable completion source, if any - HostingEnvironment.UnregisterObject(this); + if (immediate) + { + //only unregister when it's the final call, else we won't be notified of the final call + HostingEnvironment.UnregisterObject(this); + } + + if (_terminated) return; // already taken care of TaskCompletionSource terminatedSource; lock (_locker) @@ -747,7 +823,9 @@ namespace Umbraco.Web.Scheduling OnEvent(Terminated, "Terminated"); - terminatedSource.SetResult(0); + terminatedSource.TrySetResult(0); } + + #endregion } } diff --git a/src/Umbraco.Web/Search/SearchableTreeAttribute.cs b/src/Umbraco.Web/Search/SearchableTreeAttribute.cs index a2311ae989..d81a85bb4b 100644 --- a/src/Umbraco.Web/Search/SearchableTreeAttribute.cs +++ b/src/Umbraco.Web/Search/SearchableTreeAttribute.cs @@ -1,5 +1,4 @@ using System; -using Umbraco.Core.Exceptions; namespace Umbraco.Web.Search { @@ -8,42 +7,47 @@ namespace Umbraco.Web.Search { public const int DefaultSortOrder = 1000; - /// - /// This constructor defines both the angular service and method name to use - /// - /// - /// - public SearchableTreeAttribute(string serviceName, string methodName) : this(serviceName, methodName, DefaultSortOrder) { } - - /// - /// This constructor defines both the angular service and method name to use and explicitly defines a sort order for the results - /// - /// - /// - /// - public SearchableTreeAttribute(string serviceName, string methodName, int sortOrder) - { - if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); - if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentNullOrEmptyException(nameof(methodName)); - MethodName = methodName; - SortOrder = sortOrder; - ServiceName = serviceName; - } - - /// - /// This constructor will assume that the method name equals `format(searchResult, appAlias, treeAlias)` - /// - /// - public SearchableTreeAttribute(string serviceName) - { - if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); - MethodName = ""; - ServiceName = serviceName; - SortOrder = DefaultSortOrder; - } + public string ServiceName { get; } public string MethodName { get; } - public string ServiceName { get; } + public int SortOrder { get; } + + /// + /// This constructor will assume that the method name equals `format(searchResult, appAlias, treeAlias)`. + /// + /// Name of the service. + public SearchableTreeAttribute(string serviceName) + : this(serviceName, string.Empty) + { } + + /// + /// This constructor defines both the Angular service and method name to use. + /// + /// Name of the service. + /// Name of the method. + public SearchableTreeAttribute(string serviceName, string methodName) + : this(serviceName, methodName, DefaultSortOrder) + { } + + /// + /// This constructor defines both the Angular service and method name to use and explicitly defines a sort order for the results + /// + /// Name of the service. + /// Name of the method. + /// The sort order. + /// serviceName + /// or + /// methodName + /// Value can't be empty or consist only of white-space characters. - serviceName + public SearchableTreeAttribute(string serviceName, string methodName, int sortOrder) + { + if (serviceName == null) throw new ArgumentNullException(nameof(serviceName)); + if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(serviceName)); + + ServiceName = serviceName; + MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); + SortOrder = sortOrder; + } } } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index b351cca972..463f4b09df 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.Search string type; var indexName = Constants.UmbracoIndexes.InternalIndexName; - var fields = new[] { "id", "__NodeId", "__Key" }; + var fields = new List { "id", "__NodeId", "__Key" }; // TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string // manipulation for things like start paths, member types, etc... @@ -87,7 +87,7 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Member: indexName = Constants.UmbracoIndexes.MembersIndexName; type = "member"; - fields = new[] { "id", "__NodeId", "__Key", "email", "loginName" }; + fields.AddRange(new[]{ "email", "loginName"}); if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1") { sb.Append("+__NodeTypeAlias:"); @@ -97,6 +97,7 @@ namespace Umbraco.Web.Search break; case UmbracoEntityTypes.Media: type = "media"; + fields.AddRange(new[] { UmbracoExamineIndex.UmbracoFileFieldName }); var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; @@ -161,7 +162,7 @@ namespace Umbraco.Web.Search return _mapper.MapEnumerable(results); } - private bool BuildQuery(StringBuilder sb, string query, string searchFrom, string[] fields, string type) + private bool BuildQuery(StringBuilder sb, string query, string searchFrom, List fields, string type) { //build a lucene query: // the nodeName will be boosted 10x without wildcards @@ -234,11 +235,26 @@ namespace Umbraco.Web.Search foreach (var f in fields) { + var queryWordsReplaced = new string[querywords.Length]; + + // when searching file names containing hyphens we need to replace the hyphens with spaces + if (f.Equals(UmbracoExamineIndex.UmbracoFileFieldName)) + { + for (var index = 0; index < querywords.Length; index++) + { + queryWordsReplaced[index] = querywords[index].Replace("\\-", " ").Replace("_", " ").Trim(" "); + } + } + else + { + queryWordsReplaced = querywords; + } + //additional fields normally sb.Append(f); sb.Append(":"); sb.Append("("); - foreach (var w in querywords) + foreach (var w in queryWordsReplaced) { sb.Append(w.ToLower()); sb.Append("* "); diff --git a/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs b/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs index b1b2755f4c..568bfc1a26 100644 --- a/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs +++ b/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs @@ -75,7 +75,8 @@ namespace Umbraco.Web.Security /// public static void ForUmbracoBackOffice(this AuthenticationOptions options, string style, string icon, string callbackPath = null) { - if (string.IsNullOrEmpty(options.AuthenticationType)) throw new ArgumentNullOrEmptyException("options.AuthenticationType"); + if (options == null) throw new ArgumentNullException(nameof(options)); + if (string.IsNullOrEmpty(options.AuthenticationType)) throw new InvalidOperationException("The authentication type can't be null or empty."); //Ensure the prefix is set if (options.AuthenticationType.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix) == false) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5eca5ad7fe..4a1cf2c4c8 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -61,9 +61,9 @@ - + - + 2.7.0.100 diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 191fb9dcd6..94403bc1be 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Web; using Umbraco.Core; +using Umbraco.Core.Logging.Serilog; using Umbraco.Web.Runtime; namespace Umbraco.Web @@ -12,7 +13,8 @@ namespace Umbraco.Web { protected override IRuntime GetRuntime() { - return new WebRuntime(this); + var logger = SerilogLogger.CreateWithDefaultConfiguration(); + return new WebRuntime(this, logger, new MainDom(logger)); } /// diff --git a/src/Umbraco.Web/UmbracoComponentRenderer.cs b/src/Umbraco.Web/UmbracoComponentRenderer.cs index 01c696fd2d..0373c73724 100644 --- a/src/Umbraco.Web/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Web/UmbracoComponentRenderer.cs @@ -79,7 +79,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(int contentId, string alias, object parameters) { - return RenderMacro(contentId, alias, parameters.ToDictionary()); + return RenderMacro(contentId, alias, parameters?.ToDictionary()); } /// @@ -94,12 +94,12 @@ namespace Umbraco.Web if (contentId == default) throw new ArgumentException("Invalid content id " + contentId); - var content = _umbracoContextAccessor.UmbracoContext.Content?.GetById(true, contentId); + var content = _umbracoContextAccessor.UmbracoContext.Content?.GetById(contentId); if (content == null) throw new InvalidOperationException("Cannot render a macro, no content found by id " + contentId); - return RenderMacro(alias, parameters, content); + return RenderMacro(content, alias, parameters); } /// @@ -109,61 +109,17 @@ namespace Umbraco.Web /// The parameters. /// The content used for macro rendering /// - private IHtmlString RenderMacro(string alias, IDictionary parameters, IPublishedContent content) + private IHtmlString RenderMacro(IPublishedContent content, string alias, IDictionary parameters) { if (content == null) throw new ArgumentNullException(nameof(content)); // TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method looks for a lower case match. the whole macro concept needs to be rewritten. //NOTE: the value could have HTML encoded values, so we need to deal with that - var macroProps = parameters.ToDictionary( + var macroProps = parameters?.ToDictionary( x => x.Key.ToLowerInvariant(), i => (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); - var macroControl = _macroRenderer.Render(alias, content, macroProps).GetAsControl(); - - string html; - if (macroControl is LiteralControl control) - { - // no need to execute, we already have text - html = control.Text; - } - else - { - using (var containerPage = new FormlessPage()) - { - containerPage.Controls.Add(macroControl); - - using (var output = new StringWriter()) - { - // .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output' - // and I do not see how we could wire the trace context to the current context... so it creates dirty - // trace output right in the middle of the page. - // - // The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards - // which means trace output is lost if the macro is a control (.ascx or user control) that is invoked - // from within Razor -- which makes sense anyway because the control can _not_ run correctly from - // within Razor since it will never be inserted into the page pipeline (which may even not exist at all - // if we're running MVC). - // - // I'm sure there's more things that will get lost with this context changing but I guess we'll figure - // those out as we go along. One thing we lose is the content type response output. - // http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So - // here we'll save the content type response and reset it after execute is called. - - var contentType = _umbracoContextAccessor.UmbracoContext.HttpContext.Response.ContentType; - var traceIsEnabled = containerPage.Trace.IsEnabled; - containerPage.Trace.IsEnabled = false; - _umbracoContextAccessor.UmbracoContext.HttpContext.Server.Execute(containerPage, output, true); - containerPage.Trace.IsEnabled = traceIsEnabled; - //reset the content type - _umbracoContextAccessor.UmbracoContext.HttpContext.Response.ContentType = contentType; - - //Now, we need to ensure that local links are parsed - html = _linkParser.EnsureInternalLinks(output.ToString()); - } - } - - } + string html = _macroRenderer.Render(alias, content, macroProps).GetAsText(); return new HtmlString(html); } diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 367d90a504..1bc8df5b24 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -146,7 +146,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias) { - return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, new { }); + return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, null); } /// @@ -157,7 +157,7 @@ namespace Umbraco.Web /// public IHtmlString RenderMacro(string alias, object parameters) { - return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters.ToDictionary()); + return ComponentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters?.ToDictionary()); } /// diff --git a/src/Umbraco.Web/UrlHelperExtensions.cs b/src/Umbraco.Web/UrlHelperExtensions.cs index 249ce76193..7e63ff16b8 100644 --- a/src/Umbraco.Web/UrlHelperExtensions.cs +++ b/src/Umbraco.Web/UrlHelperExtensions.cs @@ -2,15 +2,12 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; -using System.Management.Instrumentation; using System.Web.Mvc; using System.Web.Routing; using ClientDependency.Core.Config; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Exceptions; using Umbraco.Web.Composing; -using Umbraco.Web.Editors; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -70,7 +67,8 @@ namespace Umbraco.Web /// public static string GetUmbracoApiService(this UrlHelper url, string actionName, Type apiControllerType, RouteValueDictionary routeVals = null) { - if (string.IsNullOrEmpty(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); + if (actionName == null) throw new ArgumentNullException(nameof(actionName)); + if (string.IsNullOrEmpty(actionName)) throw new ArgumentException("Value can't be empty.", nameof(actionName)); if (apiControllerType == null) throw new ArgumentNullException(nameof(apiControllerType)); var area = ""; @@ -99,8 +97,10 @@ namespace Umbraco.Web /// public static string GetUmbracoApiService(this UrlHelper url, string actionName, string controllerName, string area, RouteValueDictionary routeVals = null) { - if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); - if (string.IsNullOrEmpty(actionName)) throw new ArgumentNullOrEmptyException(nameof(actionName)); + if (actionName == null) throw new ArgumentNullException(nameof(actionName)); + if (string.IsNullOrEmpty(actionName)) throw new ArgumentException("Value can't be empty.", nameof(actionName)); + if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentException("Value can't be empty.", nameof(controllerName)); if (routeVals == null) { diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs index 48c26c93dd..8e410bf584 100644 --- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -4,8 +4,6 @@ using System.Linq; using System.Web; using System.Web.Mvc; using Umbraco.Core; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Web.Composing; @@ -302,8 +300,10 @@ namespace Umbraco.Web /// public static string SurfaceAction(this UrlHelper url, string action, string controllerName, string area, object additionalRouteVals) { - if (string.IsNullOrEmpty(action)) throw new ArgumentNullOrEmptyException(nameof(action)); - if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); + if (action == null) throw new ArgumentNullException(nameof(action)); + if (string.IsNullOrEmpty(action)) throw new ArgumentException("Value can't be empty.", nameof(action)); + if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentException("Value can't be empty.", nameof(controllerName)); var encryptedRoute = CreateEncryptedRouteString(controllerName, action, area, additionalRouteVals); @@ -333,7 +333,8 @@ namespace Umbraco.Web /// public static string SurfaceAction(this UrlHelper url, string action, Type surfaceType, object additionalRouteVals) { - if (string.IsNullOrEmpty(action)) throw new ArgumentNullOrEmptyException(nameof(action)); + if (action == null) throw new ArgumentNullException(nameof(action)); + if (string.IsNullOrEmpty(action)) throw new ArgumentException("Value can't be empty.", nameof(action)); if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType)); var area = ""; @@ -392,8 +393,10 @@ namespace Umbraco.Web /// internal static string CreateEncryptedRouteString(string controllerName, string controllerAction, string area, object additionalRouteVals = null) { - if (string.IsNullOrEmpty(controllerName)) throw new ArgumentNullOrEmptyException(nameof(controllerName)); - if (string.IsNullOrEmpty(controllerAction)) throw new ArgumentNullOrEmptyException(nameof(controllerAction)); + if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); + if (string.IsNullOrEmpty(controllerName)) throw new ArgumentException("Value can't be empty.", nameof(controllerName)); + if (controllerAction == null) throw new ArgumentNullException(nameof(controllerAction)); + if (string.IsNullOrEmpty(controllerAction)) throw new ArgumentException("Value can't be empty.", nameof(controllerAction)); if (area == null) throw new ArgumentNullException(nameof(area)); //need to create a params string as Base64 to put into our hidden field to use during the routes diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index efee045890..28f09b46b7 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -1,16 +1,13 @@ using System; +using System.Net; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; -using Umbraco.Core.Exceptions; -using Umbraco.Web.Composing; -using Umbraco.Web.Editors; - using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Web.Actions; using Umbraco.Core.Security; -using System.Net; +using Umbraco.Web.Actions; +using Umbraco.Web.Composing; namespace Umbraco.Web.WebApi.Filters { @@ -46,7 +43,9 @@ namespace Umbraco.Web.WebApi.Filters public EnsureUserPermissionForContentAttribute(string paramName) { - if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName)); + if (paramName == null) throw new ArgumentNullException(nameof(paramName)); + if (string.IsNullOrEmpty(paramName)) throw new ArgumentException("Value can't be empty.", nameof(paramName)); + _paramName = paramName; _permissionToCheck = ActionBrowse.ActionLetter; } diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs index 24bf2ea9a8..60e2889fd5 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs @@ -3,7 +3,6 @@ using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using Umbraco.Core; -using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Web.Composing; using Umbraco.Web.Editors; @@ -38,14 +37,18 @@ namespace Umbraco.Web.WebApi.Filters public EnsureUserPermissionForMediaAttribute(string paramName) { - if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName)); + if (paramName == null) throw new ArgumentNullException(nameof(paramName)); + if (string.IsNullOrEmpty(paramName)) throw new ArgumentException("Value can't be empty.", nameof(paramName)); + _paramName = paramName; } // TODO: v8 guess this is not used anymore, source is ignored?! public EnsureUserPermissionForMediaAttribute(string paramName, DictionarySource source) { - if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName)); + if (paramName == null) throw new ArgumentNullException(nameof(paramName)); + if (string.IsNullOrEmpty(paramName)) throw new ArgumentException("Value can't be empty.", nameof(paramName)); + _paramName = paramName; } diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs index 713bce7d1b..7f05d59a18 100644 --- a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormatAttribute.cs @@ -1,10 +1,8 @@ -using Newtonsoft.Json.Converters; -using System; +using System; using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Controllers; -using Umbraco.Core; -using Umbraco.Core.Exceptions; +using Newtonsoft.Json.Converters; namespace Umbraco.Web.WebApi.Filters { @@ -21,7 +19,9 @@ namespace Umbraco.Web.WebApi.Filters /// public JsonDateTimeFormatAttributeAttribute(string format) { - if (string.IsNullOrEmpty(format)) throw new ArgumentNullOrEmptyException(nameof(format)); + if (format == null) throw new ArgumentNullException(nameof(format)); + if (string.IsNullOrEmpty(format)) throw new ArgumentException("Value can't be empty.", nameof(format)); + _format = format; }