Merge branch 'temp8' into temp8-ui-cleanup

This commit is contained in:
Mads Rasmussen
2018-09-06 16:32:47 +02:00
283 changed files with 5240 additions and 4264 deletions

View File

@@ -19,4 +19,4 @@ using System.Resources;
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.0.0")]
[assembly: AssemblyInformationalVersion("8.0.0-alpha.49")]
[assembly: AssemblyInformationalVersion("8.0.0-alpha.50")]

View File

@@ -3,7 +3,6 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
using Umbraco.Core.Components;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
@@ -11,7 +10,7 @@ using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Auditing
namespace Umbraco.Core.Components
{
public sealed class AuditEventsComponent : UmbracoComponentBase, IUmbracoCoreComponent
{
@@ -53,18 +52,6 @@ namespace Umbraco.Core.Auditing
_userService = userService;
_entityService = entityService;
//BackOfficeUserManager.AccountLocked += ;
//BackOfficeUserManager.AccountUnlocked += ;
BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest;
BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange;
BackOfficeUserManager.LoginFailed += OnLoginFailed;
//BackOfficeUserManager.LoginRequiresVerification += ;
BackOfficeUserManager.LoginSuccess += OnLoginSuccess;
BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess;
BackOfficeUserManager.PasswordChanged += OnPasswordChanged;
BackOfficeUserManager.PasswordReset += OnPasswordReset;
//BackOfficeUserManager.ResetAccessFailedCount += ;
UserService.SavedUserGroup += OnSavedUserGroupWithUsers;
UserService.SavedUser += OnSavedUser;
@@ -255,64 +242,6 @@ namespace Umbraco.Core.Auditing
"umbraco/user/delete", "delete user");
}
private void OnLoginSuccess(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs)
{
var performingUser = GetPerformingUser(identityArgs.PerformingUser);
WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/login", "login success");
}
}
private void OnLogoutSuccess(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs)
{
var performingUser = GetPerformingUser(identityArgs.PerformingUser);
WriteAudit(performingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/sign-in/logout", "logout success");
}
}
private void OnPasswordReset(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/reset", "password reset");
}
}
private void OnPasswordChanged(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/change", "password change");
}
}
private void OnLoginFailed(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, 0, identityArgs.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: "");
}
}
private void OnForgotPasswordChange(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change");
}
}
private void OnForgotPasswordRequest(object sender, EventArgs args)
{
if (args is IdentityAuditEventArgs identityArgs && identityArgs.PerformingUser >= 0)
{
WriteAudit(identityArgs.PerformingUser, identityArgs.AffectedUser, identityArgs.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request");
}
}
private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null)
{
var performingUser = _userService.GetUserById(performingId);

View File

@@ -1,11 +1,9 @@
using System.IO;
using Umbraco.Core.Components;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
namespace Umbraco.Core.Strategies
namespace Umbraco.Core.Components
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class ManifestWatcherComponent : UmbracoComponentBase, IUmbracoCoreComponent

View File

@@ -1,11 +1,9 @@
using System;
using Umbraco.Core.Components;
using Umbraco.Core.Composing;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Strategies
namespace Umbraco.Core.Components
{
//TODO: This should just exist in the content service/repo!
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]

View File

@@ -1,13 +1,11 @@
using System;
using System.Linq;
using Umbraco.Core.Components;
using System.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Strategies
namespace Umbraco.Core.Components
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public sealed class RelateOnTrashComponent : UmbracoComponentBase, IUmbracoCoreComponent

View File

@@ -58,6 +58,7 @@ namespace Umbraco.Core.Composing
// resets *everything* that is 'current'
internal static void Reset()
{
_container?.Dispose();
_container = null;
_shortStringHelper = null;

View File

@@ -1,9 +1,24 @@
using System.Linq;
using System;
using System.Linq;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public static class ContentSectionExtensions
{
/// <summary>
/// Gets a value indicating whether the file extension corresponds to an image.
/// </summary>
/// <param name="extension">The file extension.</param>
/// <param name="contentConfig"></param>
/// <returns>A value indicating whether the file extension corresponds to an image.</returns>
public static bool IsImageFile(this IContentSection contentConfig, string extension)
{
if (contentConfig == null) throw new ArgumentNullException(nameof(contentConfig));
if (extension == null) return false;
extension = extension.TrimStart('.');
return contentConfig.ImageFileTypes.InvariantContains(extension);
}
/// <summary>
/// Determines if file extension is allowed for upload based on (optional) white list and black list
/// held in settings.
@@ -15,5 +30,17 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
(contentSection.AllowedUploadFiles.Any() == false &&
contentSection.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false);
}
/// <summary>
/// Gets the auto-fill configuration for a specified property alias.
/// </summary>
/// <param name="contentSection"></param>
/// <param name="propertyTypeAlias">The property type alias.</param>
/// <returns>The auto-fill configuration for the specified property alias, or null.</returns>
public static IImagingAutoFillUploadField GetConfig(this IContentSection contentSection, string propertyTypeAlias)
{
var autoFillConfigs = contentSection.ImageAutoFillProperties;
return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias);
}
}
}

View File

@@ -8,6 +8,8 @@
bool DisableAlternativeTemplates { get; }
bool ValidateAlternativeTemplates { get; }
bool DisableFindContentByIdPath { get; }
bool DisableRedirectUrlTracking { get; }
@@ -16,5 +18,4 @@
string UmbracoApplicationUrl { get; }
}
}

View File

@@ -5,44 +5,27 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
internal class WebRoutingElement : ConfigurationElement, IWebRoutingSection
{
[ConfigurationProperty("trySkipIisCustomErrors", DefaultValue = "false")]
public bool TrySkipIisCustomErrors
{
get { return (bool) base["trySkipIisCustomErrors"]; }
}
public bool TrySkipIisCustomErrors => (bool) base["trySkipIisCustomErrors"];
[ConfigurationProperty("internalRedirectPreservesTemplate", DefaultValue = "false")]
public bool InternalRedirectPreservesTemplate
{
get { return (bool) base["internalRedirectPreservesTemplate"]; }
}
public bool InternalRedirectPreservesTemplate => (bool) base["internalRedirectPreservesTemplate"];
[ConfigurationProperty("disableAlternativeTemplates", DefaultValue = "false")]
public bool DisableAlternativeTemplates
{
get { return (bool) base["disableAlternativeTemplates"]; }
}
public bool DisableAlternativeTemplates => (bool) base["disableAlternativeTemplates"];
[ConfigurationProperty("validateAlternativeTemplates", DefaultValue = "false")]
public bool ValidateAlternativeTemplates => (bool) base["validateAlternativeTemplates"];
[ConfigurationProperty("disableFindContentByIdPath", DefaultValue = "false")]
public bool DisableFindContentByIdPath
{
get { return (bool) base["disableFindContentByIdPath"]; }
}
public bool DisableFindContentByIdPath => (bool) base["disableFindContentByIdPath"];
[ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")]
public bool DisableRedirectUrlTracking
{
get { return (bool) base["disableRedirectUrlTracking"]; }
}
public bool DisableRedirectUrlTracking => (bool) base["disableRedirectUrlTracking"];
[ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")]
public string UrlProviderMode
{
get { return (string) base["urlProviderMode"]; }
}
public string UrlProviderMode => (string) base["urlProviderMode"];
[ConfigurationProperty("umbracoApplicationUrl", DefaultValue = null)]
public string UmbracoApplicationUrl
{
get { return (string)base["umbracoApplicationUrl"]; }
}
public string UmbracoApplicationUrl => (string)base["umbracoApplicationUrl"];
}
}

View File

@@ -22,7 +22,7 @@ namespace Umbraco.Core.Configuration
/// <summary>
/// Gets the version comment of the executing code (eg "beta").
/// </summary>
public static string CurrentComment => "alpha.49";
public static string CurrentComment => "alpha.50";
/// <summary>
/// Gets the assembly version of Umbraco.Code.dll.

View File

@@ -7,12 +7,13 @@ using System.Web;
using System.Xml.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Models
namespace Umbraco.Core
{
public static class ContentExtensions
{
@@ -241,30 +242,6 @@ namespace Umbraco.Core.Models
#region SetValue for setting file contents
/// <summary>
/// Sets the posted file value of a property.
/// </summary>
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null)
{
// ensure we get the filename without the path in IE in intranet mode
// http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
var filename = value.FileName;
var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture);
if (pos > 0)
filename = filename.Substring(pos + 1);
// strip any directory info
pos = filename.LastIndexOf(IOHelper.DirSepChar);
if (pos > 0)
filename = filename.Substring(pos + 1);
// get a safe & clean filename
filename = IOHelper.SafeFileName(filename);
if (string.IsNullOrWhiteSpace(filename)) return;
filename = filename.ToLower(); // fixme - er... why?
MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream, culture, segment);
}
/// <summary>
/// Sets the posted file value of a property.
@@ -281,7 +258,31 @@ namespace Umbraco.Core.Models
if (string.IsNullOrWhiteSpace(filename)) return;
filename = filename.ToLower(); // fixme - er... why?
MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment);
SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment);
}
private static void SetUploadFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null)
{
var property = GetProperty(content, propertyTypeAlias);
var oldpath = property.GetValue(culture, segment) is string svalue ? MediaFileSystem.GetRelativePath(svalue) : null;
var filepath = MediaFileSystem.StoreFile(content, property.PropertyType, filename, filestream, oldpath);
property.SetValue(MediaFileSystem.GetUrl(filepath), culture, segment);
}
// gets or creates a property for a content item.
private static Property GetProperty(IContentBase content, string propertyTypeAlias)
{
var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
if (property != null) return property;
var propertyType = content.GetContentType().CompositionPropertyTypes
.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
if (propertyType == null)
throw new Exception("No property type exists with alias " + propertyTypeAlias + ".");
property = new Property(propertyType);
content.Properties.Add(property);
return property;
}
/// <summary>

View File

@@ -1,20 +1,15 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LightInject;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Composing;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO.MediaPathSchemes;
using Umbraco.Core.Logging;
using Umbraco.Core.Media;
using Umbraco.Core.Media.Exif;
using Umbraco.Core.Models;
namespace Umbraco.Core.IO
@@ -32,8 +27,6 @@ namespace Umbraco.Core.IO
// dependencies, so we have to rely on property injection for anything we might need
Current.Container.InjectProperties(this);
MediaPathScheme.Initialize(this);
UploadAutoFillProperties = new UploadAutoFillProperties(this, Logger, ContentConfig);
}
[Inject]
@@ -44,9 +37,7 @@ namespace Umbraco.Core.IO
[Inject]
internal ILogger Logger { get; set; }
internal UploadAutoFillProperties UploadAutoFillProperties { get; }
/// <summary>
/// Deletes all files passed in.
/// </summary>
@@ -213,113 +204,9 @@ namespace Umbraco.Core.IO
return filepath;
}
// gets or creates a property for a content item.
private static Property GetProperty(IContentBase content, string propertyTypeAlias)
{
var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
if (property != null) return property;
var propertyType = content.GetContentType().CompositionPropertyTypes
.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
if (propertyType == null)
throw new Exception("No property type exists with alias " + propertyTypeAlias + ".");
property = new Property(propertyType);
content.Properties.Add(property);
return property;
}
// fixme - what's below belongs to the upload property editor, not the media filesystem!
public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null)
{
var property = GetProperty(content, propertyTypeAlias);
var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null;
var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath);
property.SetValue(GetUrl(filepath), culture, segment);
SetUploadFile(content, property, filepath, filestream, culture, segment);
}
public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath, string culture = null, string segment = null)
{
var property = GetProperty(content, propertyTypeAlias);
// fixme delete?
var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null;
if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath)
DeleteFile(oldpath);
property.SetValue(GetUrl(filepath), culture, segment);
using (var filestream = OpenFile(filepath))
{
SetUploadFile(content, property, filepath, filestream, culture, segment);
}
}
// sets a file for the FileUpload property editor
// ie generates thumbnails and populates autofill properties
private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream, string culture = null, string segment = null)
{
// will use filepath for extension, and filestream for length
UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, culture, segment);
}
#endregion
#region Image
/// <summary>
/// Gets a value indicating whether the file extension corresponds to an image.
/// </summary>
/// <param name="extension">The file extension.</param>
/// <returns>A value indicating whether the file extension corresponds to an image.</returns>
public bool IsImageFile(string extension)
{
if (extension == null) return false;
extension = extension.TrimStart('.');
return ContentConfig.ImageFileTypes.InvariantContains(extension);
}
/// <summary>
/// Gets the dimensions of an image.
/// </summary>
/// <param name="stream">A stream containing the image bytes.</param>
/// <returns>The dimension of the image.</returns>
/// <remarks>First try with EXIF as it is faster and does not load the entire image
/// in memory. Fallback to GDI which means loading the image in memory and thus
/// use potentially large amounts of memory.</remarks>
public Size GetDimensions(Stream stream)
{
//Try to load with exif
try
{
var jpgInfo = ImageFile.FromStream(stream);
if (jpgInfo.Format != ImageFileFormat.Unknown
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension)
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension))
{
var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value);
var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value);
if (height > 0 && width > 0)
{
return new Size(width, height);
}
}
}
catch (Exception)
{
//We will just swallow, just means we can't read exif data, we don't want to log an error either
}
//we have no choice but to try to read in via GDI
using (var image = Image.FromStream(stream))
{
var fileWidth = image.Width;
var fileHeight = image.Height;
return new Size(fileWidth, fileHeight);
}
}
#endregion
}
}

View File

@@ -40,6 +40,7 @@ namespace Umbraco.Core.IO
public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco");
[Obsolete("This will be removed, there is no more umbraco_client folder")]
public static string UmbracoClient => IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client");
public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols");

View File

@@ -7,6 +7,9 @@ namespace Umbraco.Core.Logging
/// </summary>
public class DebugDiagnosticsLogger : ILogger
{
public bool IsEnabled(Type reporting, LogLevel level)
=> true;
/// <inheritdoc/>
public void Fatal(Type reporting, Exception exception, string message)
{
@@ -26,15 +29,15 @@ namespace Umbraco.Core.Logging
}
/// <inheritdoc/>
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] args)
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(string.Format(messageTemplate, args) + Environment.NewLine + exception, reporting.FullName);
System.Diagnostics.Debug.WriteLine(MessageTemplates.Render(messageTemplate, propertyValues) + Environment.NewLine + exception, reporting.FullName);
}
/// <inheritdoc/>
public void Fatal(Type reporting, string messageTemplate, params object[] args)
public void Fatal(Type reporting, string messageTemplate, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(messageTemplate, args);
System.Diagnostics.Debug.WriteLine(messageTemplate, propertyValues);
}
/// <inheritdoc/>
@@ -56,27 +59,27 @@ namespace Umbraco.Core.Logging
}
/// <inheritdoc/>
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] args)
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(string.Format(messageTemplate, args) + Environment.NewLine + exception, reporting.FullName);
System.Diagnostics.Debug.WriteLine(MessageTemplates.Render(messageTemplate, propertyValues) + Environment.NewLine + exception, reporting.FullName);
}
/// <inheritdoc/>
public void Error(Type reporting, string messageTemplate, params object[] args)
public void Error(Type reporting, string messageTemplate, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(messageTemplate, args);
System.Diagnostics.Debug.WriteLine(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Warn(Type reporting, string format)
public void Warn(Type reporting, string message)
{
System.Diagnostics.Debug.WriteLine(format, reporting.FullName);
System.Diagnostics.Debug.WriteLine(message, reporting.FullName);
}
/// <inheritdoc/>
public void Warn(Type reporting, string format, params object[] args)
public void Warn(Type reporting, string message, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(string.Format(format, args), reporting.FullName);
System.Diagnostics.Debug.WriteLine(MessageTemplates.Render(message, propertyValues), reporting.FullName);
}
/// <inheritdoc/>
@@ -86,9 +89,9 @@ namespace Umbraco.Core.Logging
}
/// <inheritdoc/>
public void Warn(Type reporting, Exception exception, string format, params object[] args)
public void Warn(Type reporting, Exception exception, string message, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(string.Format(format + Environment.NewLine + exception, args), reporting.FullName);
System.Diagnostics.Debug.WriteLine(MessageTemplates.Render(message + Environment.NewLine + exception, propertyValues), reporting.FullName);
}
/// <inheritdoc/>
@@ -98,9 +101,9 @@ namespace Umbraco.Core.Logging
}
/// <inheritdoc/>
public void Info(Type reporting, string format, params object[] args)
public void Info(Type reporting, string messageTemplate, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(string.Format(format, args), reporting.FullName);
System.Diagnostics.Debug.WriteLine(MessageTemplates.Render(messageTemplate, propertyValues), reporting.FullName);
}
/// <inheritdoc/>
@@ -110,9 +113,9 @@ namespace Umbraco.Core.Logging
}
/// <inheritdoc/>
public void Debug(Type reporting, string format, params object[] args)
public void Debug(Type reporting, string messageTemplate, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(string.Format(format, args), reporting.FullName);
System.Diagnostics.Debug.WriteLine(MessageTemplates.Render(messageTemplate, propertyValues), reporting.FullName);
}
/// <inheritdoc/>
@@ -122,10 +125,9 @@ namespace Umbraco.Core.Logging
}
/// <inheritdoc/>
public void Verbose(Type reporting, string format, params object[] args)
public void Verbose(Type reporting, string messageTemplate, params object[] propertyValues)
{
System.Diagnostics.Debug.WriteLine(string.Format(format, args), reporting.FullName);
System.Diagnostics.Debug.WriteLine(MessageTemplates.Render(messageTemplate, propertyValues), reporting.FullName);
}
}
}

View File

@@ -9,7 +9,7 @@ namespace Umbraco.Core.Logging
public class DisposableTimer : DisposableObjectSlim
{
private readonly ILogger _logger;
private readonly LogType? _logType;
private readonly LogLevel _level;
private readonly Type _loggerType;
private readonly int _thresholdMilliseconds;
private readonly IDisposable _profilerStep;
@@ -19,22 +19,14 @@ namespace Umbraco.Core.Logging
private bool _failed;
private readonly string _timingId;
internal enum LogType
{
Debug, Info
}
// internal - created by profiling logger
internal DisposableTimer(ILogger logger, LogType logType, IProfiler profiler, Type loggerType,
internal DisposableTimer(ILogger logger, LogLevel level, IProfiler profiler, Type loggerType,
string startMessage, string endMessage, string failMessage = null,
int thresholdMilliseconds = 0)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (loggerType == null) throw new ArgumentNullException(nameof(loggerType));
_logger = logger;
_logType = logType;
_loggerType = loggerType;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_level = level;
_loggerType = loggerType ?? throw new ArgumentNullException(nameof(loggerType));
_endMessage = endMessage;
_failMessage = failMessage;
_thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds;
@@ -42,16 +34,16 @@ namespace Umbraco.Core.Logging
if (thresholdMilliseconds == 0)
{
switch (logType)
switch (_level)
{
case LogType.Debug:
case LogLevel.Debug:
logger.Debug(loggerType, "[Timing {TimingId}] {StartMessage}", _timingId, startMessage);
break;
case LogType.Info:
case LogLevel.Information:
logger.Info(loggerType, "[Timing {TimingId}] {StartMessage}", _timingId, startMessage);
break;
default:
throw new ArgumentOutOfRangeException(nameof(logType));
throw new ArgumentOutOfRangeException(nameof(level));
}
}
@@ -87,19 +79,19 @@ namespace Umbraco.Core.Logging
_profilerStep?.Dispose();
if ((Stopwatch.ElapsedMilliseconds >= _thresholdMilliseconds || _failed)
&& _logType.HasValue && _loggerType != null && _logger != null
&& _loggerType != null && _logger != null
&& (_endMessage.IsNullOrWhiteSpace() == false || _failed))
{
if (_failed)
{
_logger.Error(_loggerType, _failException, "[Timing {TimingId}] {FailMessage} ({TimingDuration}ms)", _timingId, _failMessage, Stopwatch.ElapsedMilliseconds);
}
else switch (_logType)
else switch (_level)
{
case LogType.Debug:
case LogLevel.Debug:
_logger.Debug(_loggerType, "[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)", _timingId, _endMessage, Stopwatch.ElapsedMilliseconds);
break;
case LogType.Info:
case LogLevel.Information:
_logger.Info(_loggerType, "[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)", _timingId, _endMessage, Stopwatch.ElapsedMilliseconds);
break;
// filtered in the ctor

View File

@@ -14,6 +14,13 @@ namespace Umbraco.Core.Logging
/// </remarks>
public interface ILogger
{
/// <summary>
/// Determines if logging is enabled at a specified level, for a reporting type.
/// </summary>
/// <param name="reporting">The reporting type.</param>
/// <param name="level">The level.</param>
bool IsEnabled(Type reporting, LogLevel level);
/// <summary>
/// Logs a fatal message with an exception.
/// </summary>

View File

@@ -0,0 +1,15 @@
namespace Umbraco.Core.Logging
{
/// <summary>
/// Specifies the level of a log event.
/// </summary>
public enum LogLevel
{
Verbose,
Debug,
Information,
Warning,
Error,
Fatal
}
}

View File

@@ -8,63 +8,62 @@ namespace Umbraco.Core.Logging
public static class LoggerExtensions
{
/// <summary>
/// Logs an error message
/// Determines if logging is enabled at a specified level, for a reporting type.
/// </summary>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="level">The level.</param>
public static bool IsEnabled<T>(this ILogger logger, LogLevel level)
=> logger.IsEnabled(typeof(T), level);
/// <summary>
/// Logs an error message with an exception.
/// </summary>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="message">A message.</param>
/// <param name="exception">An exception.</param>
public static void Error<T>(this ILogger logger, Exception exception, string message)
{
logger.Error(typeof(T), exception, message);
}
=> logger.Error(typeof(T), exception, message);
/// <summary>
/// Logs an error message with a structured message template
/// Logs an error message with an exception.
/// </summary>
/// <typeparam name="T">The reporting type</typeparam>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="messageTemplate">A structured message template</param>
/// <param name="exception">An exception</param>
/// <param name="propertyValues">Message property values</param>
/// <param name="exception">An exception.</param>
/// <param name="messageTemplate">A message template.</param>
/// <param name="propertyValues">Property values.</param>
public static void Error<T>(this ILogger logger, Exception exception, string messageTemplate, params object[] propertyValues)
{
logger.Error(typeof(T), exception, messageTemplate, propertyValues);
}
=> logger.Error(typeof(T), exception, messageTemplate, propertyValues);
/// <summary>
/// Logs an error message NOTE: This will log an empty message string
/// Logs an error exception.
/// </summary>
/// <typeparam name="T">The reporting type</typeparam>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="exception">An exception</param>
/// <param name="exception">An exception.</param>
public static void Error<T>(this ILogger logger, Exception exception)
{
logger.Error(typeof(T), exception);
}
=> logger.Error(typeof(T), exception);
/// <summary>
/// Logs an error message WITHOUT EX
/// Logs an error message.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="logger"></param>
/// <param name="message"></param>
public static void Error<T>(this ILogger logger, string message)
{
logger.Error(typeof(T), message);
}
/// <summary>
/// Logs an error message - using a structured log message
/// </summary>
/// <typeparam name="T">The reporting type</typeparam>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="messageTemplate">A structured message template</param>
/// <param name="propertyValues">Message property values</param>
/// <param name="message">A message.</param>
public static void Error<T>(this ILogger logger, string message)
=> logger.Error(typeof(T), message);
/// <summary>
/// Logs an error message.
/// </summary>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="messageTemplate">A message template.</param>
/// <param name="propertyValues">Property values.</param>
public static void Error<T>(this ILogger logger, string messageTemplate, params object[] propertyValues)
{
logger.Error(typeof(T), messageTemplate, propertyValues);
}
=> logger.Error(typeof(T), messageTemplate, propertyValues);
/// <summary>
/// Logs a warning message.
@@ -73,46 +72,38 @@ namespace Umbraco.Core.Logging
/// <param name="logger">The logger.</param>
/// <param name="message">A message.</param>
public static void Warn<T>(this ILogger logger, string message)
{
logger.Warn(typeof(T), message);
}
=> logger.Warn(typeof(T), message);
/// <summary>
/// Logs a warning message with a structured message template
/// Logs a warning message.
/// </summary>
/// <typeparam name="T">The reporting type</typeparam>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="messageTemplate">A structured message template</param>
/// <param name="propertyValues">Message property values</param>
/// <param name="messageTemplate">A message template.</param>
/// <param name="propertyValues">Property values.</param>
public static void Warn<T>(this ILogger logger, string messageTemplate, params object[] propertyValues)
{
logger.Warn(typeof(T), messageTemplate, propertyValues);
}
=> logger.Warn(typeof(T), messageTemplate, propertyValues);
/// <summary>
/// Logs a formatted warning message with an exception.
/// Logs a warning message with an exception.
/// </summary>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="exception">An exception.</param>
/// <param name="message">A message.</param>
public static void Warn<T>(this ILogger logger, Exception exception, string message)
{
logger.Warn(typeof(T), exception, message);
}
=> logger.Warn(typeof(T), exception, message);
/// <summary>
/// Logs a warning message with an exception with a structured message template
/// Logs a warning message with an exception.
/// </summary>
/// <typeparam name="T">The reporting type</typeparam>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="exception">An exception</param>
/// <param name="messageTemplate">A structured message template</param>
/// <param name="propertyValues">Message property values</param>
/// <param name="exception">An exception.</param>
/// <param name="messageTemplate">A message template.</param>
/// <param name="propertyValues">Property values.</param>
public static void Warn<T>(this ILogger logger, Exception exception, string messageTemplate, params object[] propertyValues)
{
logger.Warn(typeof(T), exception, messageTemplate, propertyValues);
}
=> logger.Warn(typeof(T), exception, messageTemplate, propertyValues);
/// <summary>
/// Logs an information message.
@@ -121,21 +112,17 @@ namespace Umbraco.Core.Logging
/// <param name="logger">The logger.</param>
/// <param name="message">A message.</param>
public static void Info<T>(this ILogger logger, string message)
{
logger.Info(typeof(T), message);
}
=> logger.Info(typeof(T), message);
/// <summary>
/// Logs a information message with a structured message template
/// Logs a information message.
/// </summary>
/// <typeparam name="T">The reporting type</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="messageTemplate">A structured message template</param>
/// <param name="propertyValues">Message property values</param>
/// <param name="messageTemplate">A message template.</param>
/// <param name="propertyValues">Property values.</param>
public static void Info<T>(this ILogger logger, string messageTemplate, params object[] propertyValues)
{
logger.Info(typeof(T), messageTemplate, propertyValues);
}
=> logger.Info(typeof(T), messageTemplate, propertyValues);
/// <summary>
/// Logs a debugging message.
@@ -144,21 +131,17 @@ namespace Umbraco.Core.Logging
/// <param name="logger">The logger.</param>
/// <param name="message">A message.</param>
public static void Debug<T>(this ILogger logger, string message)
{
logger.Debug(typeof(T), message);
}
=> logger.Debug(typeof(T), message);
/// <summary>
/// Logs a debugging message with a structured message template
/// Logs a debugging message.
/// </summary>
/// <typeparam name="T">The reporting type</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="messageTemplate">A structured message template</param>
/// <param name="propertyValues">Message property values</param>
/// <param name="messageTemplate">A message template.</param>
/// <param name="propertyValues">Property values.</param>
public static void Debug<T>(this ILogger logger, string messageTemplate, params object[] propertyValues)
{
logger.Debug(typeof(T), messageTemplate, propertyValues);
}
=> logger.Debug(typeof(T), messageTemplate, propertyValues);
/// <summary>
/// Logs a verbose message.
@@ -167,22 +150,17 @@ namespace Umbraco.Core.Logging
/// <param name="logger">The logger.</param>
/// <param name="message">A message.</param>
public static void Verbose<T>(this ILogger logger, string message)
{
logger.Verbose(typeof(T), message);
}
=> logger.Verbose(typeof(T), message);
/// <summary>
/// Logs a Verbose message with a structured message template
/// Logs a verbose message.
/// </summary>
/// <typeparam name="T">The reporting type</typeparam>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="messageTemplate">A structured message template</param>
/// <param name="propertyValues">Message property values</param>
/// <param name="messageTemplate">A message template.</param>
/// <param name="propertyValues">Property values.</param>
public static void Verbose<T>(this ILogger logger, string messageTemplate, params object[] propertyValues)
{
logger.Verbose(typeof(T), messageTemplate, propertyValues);
}
=> logger.Verbose(typeof(T), messageTemplate, propertyValues);
/// <summary>
/// Logs a fatal message.
@@ -192,23 +170,17 @@ namespace Umbraco.Core.Logging
/// <param name="exception">An exception.</param>
/// <param name="message">A message.</param>
public static void Fatal<T>(this ILogger logger, Exception exception, string message)
{
logger.Fatal(typeof(T), exception, message);
}
=> logger.Fatal(typeof(T), exception, message);
/// <summary>
/// Logs a fatal message with a structured message template
/// Logs a fatal message.
/// </summary>
/// <typeparam name="T">The reporting type.</typeparam>
/// <param name="logger">The logger.</param>
/// <param name="exception">An exception.</param>
/// <param name="messageTemplate">A structured message template</param>
/// <param name="propertyValues">Message property values</param>
/// <param name="messageTemplate">A message template.</param>
/// <param name="propertyValues">Property values.</param>
public static void Fatal<T>(this ILogger logger, Exception exception, string messageTemplate, params object[] propertyValues)
{
logger.Fatal(typeof(T), exception, messageTemplate, propertyValues);
}
=> logger.Fatal(typeof(T), exception, messageTemplate, propertyValues);
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Linq;
using Serilog;
namespace Umbraco.Core.Logging
{
/// <summary>
/// Provides tools to support message templates.
/// </summary>
public static class MessageTemplates
{
// Umbraco now uses Message Templates (https://messagetemplates.org/) for logging, which means
// we cannot plainly use string.Format() to format them. There is a work-in-progress C# lib,
// derived from Serilog, which should help (https://github.com/messagetemplates/messagetemplates-csharp)
// but it only has a pre-release NuGet package. So, we've got to use Serilog's code, which
// means we cannot get rid of Serilog entirely. We may want to revisit this at some point.
private static readonly Lazy<global::Serilog.ILogger> MinimalLogger = new Lazy<global::Serilog.ILogger>(() => new LoggerConfiguration().CreateLogger());
public static string Render(string messageTemplate, params object[] args)
{
// by default, unless initialized otherwise, Log.Logger is SilentLogger which cannot bind message
// templates. Log.Logger is set to a true Logger when initializing Umbraco's logger, but in case
// that has not been done already - use a temp minimal logger (eg for tests).
var logger = Log.Logger as global::Serilog.Core.Logger ?? MinimalLogger.Value;
var bound = logger.BindMessageTemplate(messageTemplate, args, out var parsedTemplate, out var boundProperties);
if (!bound)
throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args.");
return parsedTemplate.Render(boundProperties.ToDictionary(x => x.Name, x => x.Value));
}
}
}

View File

@@ -13,10 +13,8 @@ namespace Umbraco.Core.Logging
public ProfilingLogger(ILogger logger, IProfiler profiler)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (profiler == null) throw new ArgumentNullException(nameof(profiler));
Logger = logger;
Profiler = profiler;
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler));
}
public DisposableTimer TraceDuration<T>(string startMessage)
@@ -26,27 +24,33 @@ namespace Umbraco.Core.Logging
public DisposableTimer TraceDuration<T>(string startMessage, string completeMessage, string failMessage = null)
{
return new DisposableTimer(Logger, DisposableTimer.LogType.Info, Profiler, typeof(T), startMessage, completeMessage, failMessage);
return new DisposableTimer(Logger, LogLevel.Information, Profiler, typeof(T), startMessage, completeMessage, failMessage);
}
public DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null)
{
return new DisposableTimer(Logger, DisposableTimer.LogType.Info, Profiler, loggerType, startMessage, completeMessage, failMessage);
return new DisposableTimer(Logger, LogLevel.Information, Profiler, loggerType, startMessage, completeMessage, failMessage);
}
public DisposableTimer DebugDuration<T>(string startMessage)
{
return DebugDuration<T>(startMessage, "Completed.");
return Logger.IsEnabled<T>(LogLevel.Debug)
? DebugDuration<T>(startMessage, "Completed.")
: null;
}
public DisposableTimer DebugDuration<T>(string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0)
{
return new DisposableTimer(Logger, DisposableTimer.LogType.Debug, Profiler, typeof(T), startMessage, completeMessage, failMessage, thresholdMilliseconds);
return Logger.IsEnabled<T>(LogLevel.Debug)
? new DisposableTimer(Logger, LogLevel.Debug, Profiler, typeof(T), startMessage, completeMessage, failMessage, thresholdMilliseconds)
: null;
}
public DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0)
{
return new DisposableTimer(Logger, DisposableTimer.LogType.Debug, Profiler, loggerType, startMessage, completeMessage, failMessage, thresholdMilliseconds);
return Logger.IsEnabled(loggerType, LogLevel.Debug)
? new DisposableTimer(Logger, LogLevel.Debug, Profiler, loggerType, startMessage, completeMessage, failMessage, thresholdMilliseconds)
: null;
}
}
}

View File

@@ -1,7 +1,7 @@
using Serilog.Core;
using Serilog.Events;
namespace Umbraco.Core.Logging.SerilogExtensions
namespace Umbraco.Core.Logging.Serilog
{
/// <summary>
/// This is used to create a new property in Logs called 'Log4NetLevel'

View File

@@ -4,7 +4,7 @@ using Serilog;
using Serilog.Events;
using Serilog.Formatting.Compact;
namespace Umbraco.Core.Logging.SerilogExtensions
namespace Umbraco.Core.Logging.Serilog
{
public static class LoggerConfigExtensions
{
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Logging.SerilogExtensions
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
public static LoggerConfiguration MinimalConfiguration(this LoggerConfiguration logConfig)
{
Serilog.Debugging.SelfLog.Enable(msg => System.Diagnostics.Debug.WriteLine(msg));
global::Serilog.Debugging.SelfLog.Enable(msg => System.Diagnostics.Debug.WriteLine(msg));
//Set this environment variable - so that it can be used in external config file
//add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" />

View File

@@ -2,41 +2,40 @@
using System.IO;
using System.Reflection;
using System.Threading;
using Umbraco.Core.Configuration;
using Umbraco.Core.Diagnostics;
using Serilog;
using Serilog.Events;
using Umbraco.Core.Logging.SerilogExtensions;
using Umbraco.Core.Configuration;
using Umbraco.Core.Diagnostics;
namespace Umbraco.Core.Logging
namespace Umbraco.Core.Logging.Serilog
{
///<summary>
/// Implements <see cref="ILogger"/> on top of Serilog.
///</summary>
public class Logger : ILogger
public class SerilogLogger : ILogger
{
/// <summary>
/// Initialize a new instance of the <see cref="Logger"/> class with a configuration file.
/// Initialize a new instance of the <see cref="SerilogLogger"/> class with a configuration file.
/// </summary>
/// <param name="logConfigFile"></param>
public Logger(FileInfo logConfigFile)
public SerilogLogger(FileInfo logConfigFile)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + logConfigFile)
.CreateLogger();
}
public Logger(LoggerConfiguration logConfig)
public SerilogLogger(LoggerConfiguration logConfig)
{
//Configure Serilog static global logger with config passed in
Log.Logger = logConfig.CreateLogger();
}
/// <summary>
/// Creates a logger with some pre-definied configuration and remainder from config file
/// Creates a logger with some pre-defined configuration and remainder from config file
/// </summary>
/// <remarks>Used by UmbracoApplicationBase to get its logger.</remarks>
public static Logger CreateWithDefaultConfiguration()
public static SerilogLogger CreateWithDefaultConfiguration()
{
var loggerConfig = new LoggerConfiguration();
loggerConfig
@@ -46,78 +45,118 @@ namespace Umbraco.Core.Logging
.ReadFromConfigFile()
.ReadFromUserConfigFile();
return new Logger(loggerConfig);
return new SerilogLogger(loggerConfig);
}
/// <summary>
/// Gets a contextualized logger.
/// </summary>
private global::Serilog.ILogger LoggerFor(Type reporting)
=> Log.Logger.ForContext(reporting);
/// <summary>
/// Maps Umbraco's log level to Serilog's.
/// </summary>
private LogEventLevel MapLevel(LogLevel level)
{
switch (level)
{
case LogLevel.Debug:
return LogEventLevel.Debug;
case LogLevel.Error:
return LogEventLevel.Error;
case LogLevel.Fatal:
return LogEventLevel.Fatal;
case LogLevel.Information:
return LogEventLevel.Information;
case LogLevel.Verbose:
return LogEventLevel.Verbose;
case LogLevel.Warning:
return LogEventLevel.Warning;
}
throw new NotSupportedException($"LogLevel \"{level}\" is not supported.");
}
/// <inheritdoc/>
public bool IsEnabled(Type reporting, LogLevel level)
=> LoggerFor(reporting).IsEnabled(MapLevel(level));
/// <inheritdoc/>
public void Fatal(Type reporting, Exception exception, string message)
{
Fatal(reporting, exception, message, null);
var logger = LoggerFor(reporting);
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref message);
logger.Fatal(exception, message);
}
/// <inheritdoc/>
public void Fatal(Type reporting, Exception exception)
{
Fatal(reporting, exception, string.Empty);
var logger = LoggerFor(reporting);
var message = "Exception.";
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref message);
logger.Fatal(exception, message);
}
/// <inheritdoc/>
public void Fatal(Type reporting, string message)
{
//Sometimes we need to throw an error without an ex
Fatal(reporting, null, message);
LoggerFor(reporting).Fatal(message);
}
/// <inheritdoc/>
public void Fatal(Type reporting, string messageTemplate, params object[] propertyValues)
{
//Log a structured message WITHOUT an ex
Fatal(reporting, null, messageTemplate, propertyValues);
LoggerFor(reporting).Fatal(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
ErrorOrFatal(Fatal, exception, ref messageTemplate);
var logger = Log.Logger;
logger?.ForContext(reporting).Fatal(exception, messageTemplate, propertyValues);
var logger = LoggerFor(reporting);
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref messageTemplate);
logger.Fatal(exception, messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Error(Type reporting, Exception exception, string message)
{
Error(reporting, exception, message, null);
var logger = LoggerFor(reporting);
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref message);
logger.Error(exception, message);
}
/// <inheritdoc/>
public void Error(Type reporting, Exception exception)
{
Error(reporting, exception, string.Empty);
var logger = LoggerFor(reporting);
var message = "Exception";
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref message);
logger.Error(exception, message);
}
/// <inheritdoc/>
public void Error(Type reporting, string message)
{
//Sometimes we need to throw an error without an ex
Error(reporting, null, message);
LoggerFor(reporting).Error(message);
}
/// <inheritdoc/>
public void Error(Type reporting, string messageTemplate, params object[] propertyValues)
{
//Log a structured message WITHOUT an ex
Error(reporting, null, messageTemplate, propertyValues);
LoggerFor(reporting).Error(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
ErrorOrFatal(Error, exception, ref messageTemplate);
var logger = Log.Logger;
logger?.ForContext(reporting).Error(exception, messageTemplate, propertyValues);
var logger = LoggerFor(reporting);
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref messageTemplate);
logger.Error(exception, messageTemplate, propertyValues);
}
private static void ErrorOrFatal(Action<Type, Exception, string, object[]> logAction, Exception exception, ref string messageTemplate)
private static void DumpThreadAborts(global::Serilog.ILogger logger, LogEventLevel level, Exception exception, ref string messageTemplate)
{
var dump = false;
@@ -143,8 +182,10 @@ namespace Umbraco.Core.Logging
}
catch (Exception ex)
{
messageTemplate += "\r\nFailed to create a minidump";
//Log a new entry (as opposed to appending to same log entry)
logAction(ex.GetType(), ex, "Failed to create a minidump at App_Data/MiniDump ({ExType}: {ExMessage}",
logger.Write(level, ex, "Failed to create a minidump ({ExType}: {ExMessage})",
new object[]{ ex.GetType().FullName, ex.Message });
}
}
@@ -174,69 +215,63 @@ namespace Umbraco.Core.Logging
}
/// <inheritdoc/>
public void Warn(Type reporting, string format)
public void Warn(Type reporting, string message)
{
Warn(reporting, null, format);
LoggerFor(reporting).Warning(message);
}
/// <inheritdoc/>
public void Warn(Type reporting, string messageTemplate, params object[] propertyValues)
public void Warn(Type reporting, string message, params object[] propertyValues)
{
Warn(reporting, null, messageTemplate, propertyValues);
LoggerFor(reporting).Warning(message, propertyValues);
}
/// <inheritdoc/>
public void Warn(Type reporting, Exception exception, string message)
{
Warn(reporting, exception, message, Array.Empty<object>());
LoggerFor(reporting).Warning(exception, message);
}
/// <inheritdoc/>
public void Warn(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
var logger = Log.Logger;
logger?.ForContext(reporting).Warning(exception, messageTemplate, propertyValues);
LoggerFor(reporting).Warning(exception, messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Info(Type reporting, string message)
{
Info(reporting, message, Array.Empty<object>());
LoggerFor(reporting).Information(message);
}
/// <inheritdoc/>
public void Info(Type reporting, string messageTemplate, params object[] propertyValues)
{
var logger = Log.Logger;
logger?.ForContext(reporting).Information(messageTemplate, propertyValues);
LoggerFor(reporting).Information(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Debug(Type reporting, string message)
{
Debug(reporting, message, Array.Empty<object>());
LoggerFor(reporting).Debug(message);
}
/// <inheritdoc/>
public void Debug(Type reporting, string messageTemplate, params object[] propertyValues)
{
var logger = Log.Logger;
logger?.ForContext(reporting).Debug(messageTemplate, propertyValues);
LoggerFor(reporting).Debug(messageTemplate, propertyValues);
}
/// <inheritdoc/>
public void Verbose(Type reporting, string message)
{
Verbose(reporting, message, Array.Empty<object>());
LoggerFor(reporting).Verbose(message);
}
/// <inheritdoc/>
public void Verbose(Type reporting, string messageTemplate, params object[] propertyValues)
{
var logger = Log.Logger;
logger?.ForContext(reporting).Verbose(messageTemplate, propertyValues);
LoggerFor(reporting).Verbose(messageTemplate, propertyValues);
}
}
}

View File

@@ -1,21 +0,0 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
namespace Umbraco.Core.Media
{
public static class ImageExtensions
{
/// <summary>
/// Gets the MIME type of an image.
/// </summary>
/// <param name="image">The image.</param>
/// <returns>The MIME type of the image.</returns>
public static string GetMimeType(this Image image)
{
var format = image.RawFormat;
var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
return codec.MimeType;
}
}
}

View File

@@ -280,26 +280,6 @@ namespace Umbraco.Core.Models
Properties.Add(property);
}
// HttpPostedFileBase is the base class that can be mocked
// HttpPostedFile is what we get in ASP.NET
// HttpPostedFileWrapper wraps sealed HttpPostedFile as HttpPostedFileBase
/// <summary>
/// Sets the posted file value of a property.
/// </summary>
public virtual void SetValue(string propertyTypeAlias, HttpPostedFile value, string culture = null, string segment = null)
{
ContentExtensions.SetValue(this, propertyTypeAlias, new HttpPostedFileWrapper(value), culture, segment);
}
/// <summary>
/// Sets the posted file value of a property.
/// </summary>
public virtual void SetValue(string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null)
{
ContentExtensions.SetValue(this, propertyTypeAlias, value, culture, segment);
}
#endregion
#region Copy

View File

@@ -98,6 +98,30 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateId">The template id to check</param>
/// <returns>True if AllowedTemplates contains the templateId else False</returns>
public bool IsAllowedTemplate(int templateId)
{
return AllowedTemplates == null
? false
: AllowedTemplates.Any(t => t.Id == templateId);
}
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateAlias">The template alias to check</param>
/// <returns>True if AllowedTemplates contains the templateAlias else False</returns>
public bool IsAllowedTemplate(string templateAlias)
{
return AllowedTemplates == null
? false
: AllowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase));
}
/// <summary>
/// Sets the default template for the ContentType
/// </summary>

View File

@@ -172,10 +172,9 @@ namespace Umbraco.Core.Models
/// </summary>
/// <returns>A value indicating whether the culture can be published.</returns>
/// <remarks>
/// <para>Fails if values cannot be published, e.g. if some values are not valid.</para>
/// <para>Fails if properties don't pass variant validtion rules.</para>
/// <para>Publishing must be finalized via the content service SavePublishing method.</para>
/// </remarks>
// fixme - should return an attempt with error results
bool PublishCulture(string culture = "*");
/// <summary>

View File

@@ -139,7 +139,7 @@ namespace Umbraco.Core.Models
// fixme validate published cultures?
/// <summary>
/// Validates the content item's properties.
/// Validates the content item's properties pass variant rules
/// </summary>
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>

View File

@@ -17,6 +17,20 @@ namespace Umbraco.Core.Models
/// </summary>
IEnumerable<ITemplate> AllowedTemplates { get; set; }
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateId">The template id to check</param>
/// <returns>True if AllowedTemplates contains the templateId else False</returns>
bool IsAllowedTemplate(int templateId);
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateAlias">The template alias to check</param>
/// <returns>True if AllowedTemplates contains the templateAlias else False</returns>
bool IsAllowedTemplate(string templateAlias);
/// <summary>
/// Sets the default template for the ContentType
/// </summary>

View File

@@ -93,14 +93,6 @@ namespace Umbraco.Core.Models.Identity
_roles.CollectionChanged += _roles_CollectionChanged;
}
public virtual async Task<ClaimsIdentity> GenerateUserIdentityAsync(BackOfficeUserManager<BackOfficeIdentityUser> manager)
{
// NOTE the authenticationType must match the umbraco one
// defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, Constants.Security.BackOfficeAuthenticationType);
return userIdentity;
}
/// <summary>
/// Returns true if an Id has been set on this object this will be false if the object is new and not peristed to the database
/// </summary>

View File

@@ -260,7 +260,7 @@ namespace Umbraco.Core.Models.Membership
{
get
{
if (LastLoginDate == default(DateTime) && IsApproved == false && InvitedDate != null)
if (LastLoginDate == default && IsApproved == false && InvitedDate != null)
return UserState.Invited;
if (IsLockedOut)
@@ -268,6 +268,10 @@ namespace Umbraco.Core.Models.Membership
if (IsApproved == false)
return UserState.Disabled;
// User is not disabled or locked and has never logged in before
if (LastLoginDate == default && IsApproved && IsLockedOut == false)
return UserState.Inactive;
return UserState.Active;
}
}

View File

@@ -9,6 +9,7 @@
Active = 0,
Disabled = 1,
LockedOut = 2,
Invited = 3
Invited = 3,
Inactive = 4
}
}

View File

@@ -1,14 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Models

View File

@@ -10,9 +10,6 @@ namespace Umbraco.Core
/// <summary>
/// Returns the machine name that is safe to use in file paths.
/// </summary>
/// <remarks>
/// see: https://github.com/Shandem/ClientDependency/issues/4
/// </remarks>
public static string FileSafeMachineName
{
get { return MachineName.ReplaceNonAlphanumericChars('-'); }

View File

@@ -150,6 +150,8 @@ UNION
SELECT '4CountOfLockedOut' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userNoConsole = 1
UNION
SELECT '5CountOfInvited' AS colName, COUNT(id) AS num FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL
UNION
SELECT '6CountOfDisabled' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL
ORDER BY colName";
var result = Database.Fetch<dynamic>(sql);
@@ -160,7 +162,8 @@ ORDER BY colName";
{UserState.Active, (int) result[1].num},
{UserState.Disabled, (int) result[2].num},
{UserState.LockedOut, (int) result[3].num},
{UserState.Invited, (int) result[4].num}
{UserState.Invited, (int) result[4].num},
{UserState.Inactive, (int) result[5].num}
};
}
@@ -766,6 +769,12 @@ ORDER BY colName";
sb.Append("(userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NOT NULL)");
appended = true;
}
if (userState.Contains(UserState.Inactive))
{
if (appended) sb.Append(" OR ");
sb.Append("(userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL)");
appended = true;
}
if (userState.Contains(UserState.Disabled))
{
if (appended) sb.Append(" OR ");

View File

@@ -143,19 +143,19 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// <returns></returns>
public IEnumerable<Tuple<string, string, string, string>> GetDefaultConstraintsPerColumn(IDatabase db)
{
var items = db.Fetch<dynamic>("SELECT TableName = t.Name,ColumnName = c.Name,dc.Name,dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id");
var items = db.Fetch<dynamic>("SELECT TableName = t.Name, ColumnName = c.Name, dc.Name, dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id INNER JOIN sys.schemas as s on t.[schema_id] = s.[schema_id] WHERE s.name = (SELECT SCHEMA_NAME())");
return items.Select(x => new Tuple<string, string, string, string>(x.TableName, x.ColumnName, x.Name, x.Definition));
}
public override IEnumerable<string> GetTablesInSchema(IDatabase db)
{
var items = db.Fetch<dynamic>("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES");
var items = db.Fetch<dynamic>("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
return items.Select(x => x.TABLE_NAME).Cast<string>().ToList();
}
public override IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db)
{
var items = db.Fetch<dynamic>("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS");
var items = db.Fetch<dynamic>("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
return
items.Select(
item =>
@@ -168,7 +168,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
{
var items =
db.Fetch<dynamic>(
"SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE");
"SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
return items.Select(item => new Tuple<string, string>(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList();
}
@@ -177,7 +177,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
{
var items =
db.Fetch<dynamic>(
"SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE");
"SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
return items.Select(item => new Tuple<string, string, string>(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)).ToList();
}
@@ -191,7 +191,8 @@ CASE WHEN I.is_unique_constraint = 1 OR I.is_unique = 1 THEN 1 ELSE 0 END AS [U
from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id]
inner join sys.index_columns as IC on IC.[object_id] = I.[object_id] and IC.[index_id] = I.[index_id]
inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id]
WHERE I.is_primary_key = 0
inner join sys.schemas as S on T.[schema_id] = S.[schema_id]
WHERE S.name = (SELECT SCHEMA_NAME()) AND I.is_primary_key = 0
order by T.name, I.name");
return items.Select(item => new Tuple<string, string, string, bool>(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME,
item.UNIQUE == 1)).ToList();
@@ -201,7 +202,7 @@ order by T.name, I.name");
public override bool DoesTableExist(IDatabase db, string tableName)
{
var result =
db.ExecuteScalar<long>("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName",
db.ExecuteScalar<long>("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @TableName AND TABLE_SCHEMA = (SELECT SCHEMA_NAME())",
new { TableName = tableName });
return result > 0;

View File

@@ -138,7 +138,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // fixme MOVE TO MODELS O
/// </summary>
/// <returns></returns>
public bool HasFocalPoint()
=> FocalPoint != null && FocalPoint.Left != 0.5m && FocalPoint.Top != 0.5m;
=> FocalPoint != null && (FocalPoint.Left != 0.5m || FocalPoint.Top != 0.5m);
/// <summary>
/// Determines whether the value has a specified crop.

View File

@@ -720,7 +720,7 @@ namespace Umbraco.Core.Security
}
else
{
//if the salt bytes is too long for the required key length for the algorithm, extend it
//if the salt bytes is too short for the required key length for the algorithm, extend it
var numArray2 = new byte[keyedHashAlgorithm.Key.Length];
var dstOffset = 0;
while (dstOffset < numArray2.Length)

View File

@@ -645,7 +645,7 @@ namespace Umbraco.Core.Services.Implement
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, member => member.Username);
return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, member => member.Name);
}
}

View File

@@ -11,6 +11,10 @@ namespace Umbraco.Core.Services
/// Represents the result of a service operation.
/// </summary>
/// <typeparam name="TResultType">The type of the result type.</typeparam>
/// <remarks>Type <typeparamref name="TResultType"/> must be an enumeration, and its
/// underlying type must be byte. Values indicating success should be in the 0-127
/// range, while values indicating failure should be in the 128-255 range. See
/// <see cref="OperationResultType"/> for a base implementation.</remarks>
public class OperationResult<TResultType>
where TResultType : struct
{
@@ -56,6 +60,10 @@ namespace Umbraco.Core.Services
/// </summary>
/// <typeparam name="TResultType">The type of the result type.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <remarks>Type <typeparamref name="TResultType"/> must be an enumeration, and its
/// underlying type must be byte. Values indicating success should be in the 0-127
/// range, while values indicating failure should be in the 128-255 range. See
/// <see cref="OperationResultType"/> for a base implementation.</remarks>
public class OperationResult<TResultType, TEntity> : OperationResult<TResultType>
where TResultType : struct
{
@@ -111,7 +119,8 @@ namespace Umbraco.Core.Services
return new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages);
}
// fixme wtf?
// fixme - this exists to support services that still return Attempt<OperationResult>
// these services should directly return an OperationResult, and then this static class should be deleted
internal static class Attempt
{
/// <summary>

View File

@@ -38,8 +38,6 @@
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.DirectoryServices.AccountManagement" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Runtime.Caching" />
@@ -58,21 +56,22 @@
<ItemGroup>
<!-- note: NuGet deals with transitive references now -->
<PackageReference Include="AutoMapper" Version="7.0.1" />
<PackageReference Include="ClientDependency" Version="1.9.6" />
<PackageReference Include="HtmlAgilityPack" Version="1.7.2" />
<PackageReference Include="ImageProcessor" Version="2.6.1.19" />
<PackageReference Include="LightInject" Version="5.1.2" />
<PackageReference Include="LightInject.Annotation" Version="1.1.0" />
<PackageReference Include="LightInject.Web" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNet.Identity.Owin" Version="2.2.1" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.6" />
<PackageReference Include="Microsoft.Owin.Security.Cookies" Version="4.0.0" />
<PackageReference Include="Microsoft.Owin.Security.OAuth" Version="4.0.0" />
<PackageReference Include="Microsoft.Web.Xdt" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNet.Identity.Core">
<Version>2.2.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNet.WebApi.Client">
<Version>5.2.6</Version>
</PackageReference>
<PackageReference Include="Microsoft.Owin">
<Version>4.0.0</Version>
</PackageReference>
<PackageReference Include="MiniProfiler" Version="3.2.0.157" />
<PackageReference Include="MySql.Data" Version="6.10.7" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NPoco" Version="3.9.3" />
<PackageReference Include="NPoco" Version="3.9.4" />
<PackageReference Include="Semver" Version="2.0.4" />
<PackageReference Include="Serilog">
<Version>2.7.1</Version>
@@ -110,8 +109,7 @@
<Compile Include="Attempt.cs" />
<Compile Include="AttemptOfTResult.cs" />
<Compile Include="AttemptOfTResultTStatus.cs" />
<Compile Include="Auditing\AuditEventsComponent.cs" />
<Compile Include="Auditing\IdentityAuditEventArgs.cs" />
<Compile Include="Components\AuditEventsComponent.cs" />
<Compile Include="BindingRedirects.cs" />
<Compile Include="ByteArrayExtensions.cs" />
<Compile Include="Cache\CacheHelper.cs" />
@@ -194,7 +192,6 @@
<Compile Include="Composing\WeightAttribute.cs" />
<Compile Include="Composing\WeightedCollectionBuilderBase.cs" />
<Compile Include="Configuration\CaseInsensitiveEnumConfigConverter.cs" />
<Compile Include="Configuration\ClientDependencyConfiguration.cs" />
<Compile Include="Configuration\CommaDelimitedConfigurationElement.cs" />
<Compile Include="Configuration\CoreDebug.cs" />
<Compile Include="Configuration\Dashboard\AccessElement.cs" />
@@ -329,8 +326,10 @@
<Compile Include="IO\MediaPathSchemes\OriginalMediaPathScheme.cs" />
<Compile Include="IO\MediaPathSchemes\TwoGuidsMediaPathScheme.cs" />
<Compile Include="KeyValuePairExtensions.cs" />
<Compile Include="Logging\SerilogExtensions\LoggerConfigExtensions.cs" />
<Compile Include="Logging\SerilogExtensions\Log4NetLevelMapperEnricher.cs" />
<Compile Include="Logging\LogLevel.cs" />
<Compile Include="Logging\MessageTemplates.cs" />
<Compile Include="Logging\Serilog\LoggerConfigExtensions.cs" />
<Compile Include="Logging\Serilog\Log4NetLevelMapperEnricher.cs" />
<Compile Include="Migrations\MigrationBase_Extra.cs" />
<Compile Include="Migrations\Upgrade\V_7_10_0\RenamePreviewFolder.cs" />
<Compile Include="Migrations\Upgrade\V_7_12_0\AddRelationTypeForMediaFolderOnDelete.cs" />
@@ -567,9 +566,8 @@
<Compile Include="ListExtensions.cs" />
<Compile Include="Logging\DebugDiagnosticsLogger.cs" />
<Compile Include="Logging\ILogger.cs" />
<Compile Include="Logging\ImageProcessorLogger.cs" />
<Compile Include="Logging\IProfiler.cs" />
<Compile Include="Logging\Logger.cs" />
<Compile Include="Logging\Serilog\SerilogLogger.cs" />
<Compile Include="Logging\LoggerExtensions.cs" />
<Compile Include="Logging\LoggingTaskExtension.cs" />
<Compile Include="Logging\LogProfiler.cs" />
@@ -582,49 +580,17 @@
<Compile Include="Logging\WebProfilerComponent.cs" />
<Compile Include="Logging\WebProfilerProvider.cs" />
<Compile Include="Macros\MacroErrorBehaviour.cs" />
<Compile Include="Macros\MacroTagParser.cs" />
<Compile Include="MainDom.cs" />
<Compile Include="Manifest\ManifestParser.cs" />
<Compile Include="Manifest\ValueValidatorConverter.cs" />
<Compile Include="Manifest\ManifestWatcher.cs" />
<Compile Include="Manifest\PackageManifest.cs" />
<Compile Include="Manifest\DataEditorConverter.cs" />
<Compile Include="Media\Exif\BitConverterEx.cs" />
<Compile Include="Media\Exif\ExifBitConverter.cs" />
<Compile Include="Media\Exif\ExifEnums.cs" />
<Compile Include="Media\Exif\ExifExceptions.cs" />
<Compile Include="Media\Exif\ExifExtendedProperty.cs" />
<Compile Include="Media\Exif\ExifFileTypeDescriptor.cs" />
<Compile Include="Media\Exif\ExifInterOperability.cs" />
<Compile Include="Media\Exif\ExifProperty.cs" />
<Compile Include="Media\Exif\ExifPropertyCollection.cs" />
<Compile Include="Media\Exif\ExifPropertyFactory.cs" />
<Compile Include="Media\Exif\ExifTag.cs" />
<Compile Include="Media\Exif\ExifTagFactory.cs" />
<Compile Include="Media\Exif\IFD.cs" />
<Compile Include="Media\Exif\ImageFile.cs" />
<Compile Include="Media\Exif\ImageFileDirectory.cs" />
<Compile Include="Media\Exif\ImageFileDirectoryEntry.cs" />
<Compile Include="Media\Exif\ImageFileFormat.cs" />
<Compile Include="Media\Exif\JFIFEnums.cs" />
<Compile Include="Media\Exif\JFIFExtendedProperty.cs" />
<Compile Include="Media\Exif\JFIFThumbnail.cs" />
<Compile Include="Media\Exif\JPEGExceptions.cs" />
<Compile Include="Media\Exif\JPEGFile.cs" />
<Compile Include="Media\Exif\JPEGMarker.cs" />
<Compile Include="Media\Exif\JPEGSection.cs" />
<Compile Include="Media\Exif\MathEx.cs" />
<Compile Include="Media\Exif\TIFFFile.cs" />
<Compile Include="Media\Exif\TIFFHeader.cs" />
<Compile Include="Media\Exif\TIFFStrip.cs" />
<Compile Include="Media\Exif\Utility.cs" />
<Compile Include="Media\IEmbedProvider.cs" />
<Compile Include="Media\IEmbedSettingProvider.cs" />
<Compile Include="Media\ImageExtensions.cs" />
<Compile Include="Media\ProviderSetting.cs" />
<Compile Include="Media\Result.cs" />
<Compile Include="Media\Status.cs" />
<Compile Include="Media\UploadAutoFillProperties.cs" />
<Compile Include="Migrations\IPostMigration.cs" />
<Compile Include="Migrations\MigrationBuilder.cs" />
<Compile Include="Migrations\MigrationPlan.cs" />
@@ -641,7 +607,7 @@
<Compile Include="Models\AuditType.cs" />
<Compile Include="Models\Content.cs" />
<Compile Include="Models\ContentBase.cs" />
<Compile Include="Models\ContentExtensions.cs" />
<Compile Include="ContentExtensions.cs" />
<Compile Include="Models\ContentStatus.cs" />
<Compile Include="Models\ContentType.cs" />
<Compile Include="Models\ContentTypeAvailableCompositionsResult.cs" />
@@ -1306,19 +1272,10 @@
<Compile Include="Scoping\ScopeContextualBase.cs" />
<Compile Include="Scoping\ScopeProvider.cs" />
<Compile Include="Scoping\ScopeReference.cs" />
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\AuthenticationExtensions.cs" />
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />
<Compile Include="Security\BackOfficeCookieAuthenticationProvider.cs" />
<Compile Include="Security\BackOfficeSignInManager.cs" />
<Compile Include="Security\BackOfficeUserManager.cs" />
<Compile Include="Security\BackOfficeUserManagerMarker.cs" />
<Compile Include="Security\BackOfficeUserPasswordCheckerResult.cs" />
<Compile Include="Security\BackOfficeUserStore.cs" />
<Compile Include="Security\BackOfficeUserValidator.cs" />
<Compile Include="Security\EmailService.cs" />
<Compile Include="Security\IBackOfficeUserManagerMarker.cs" />
<Compile Include="Security\IBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\IMembershipProviderPasswordHasher.cs" />
<Compile Include="Security\IUmbracoMemberTypeMembershipProvider.cs" />
<Compile Include="Security\IUserAwarePasswordHasher.cs" />
@@ -1329,8 +1286,6 @@
<Compile Include="Security\MembershipProviderExtensions.cs" />
<Compile Include="Security\MembershipProviderPasswordHasher.cs" />
<Compile Include="Security\MembershipProviderPasswordValidator.cs" />
<Compile Include="Security\OwinExtensions.cs" />
<Compile Include="Security\SessionIdValidator.cs" />
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
<Compile Include="Security\UmbracoEmailMessage.cs" />
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
@@ -1447,9 +1402,9 @@
<Compile Include="Services\UnpublishResultType.cs" />
<Compile Include="Services\UserServiceExtensions.cs" />
<Compile Include="Settable.cs" />
<Compile Include="Strategies\ManifestWatcherComponent.cs" />
<Compile Include="Strategies\RelateOnCopyComponent.cs" />
<Compile Include="Strategies\RelateOnTrashComponent.cs" />
<Compile Include="Components\ManifestWatcherComponent.cs" />
<Compile Include="Components\RelateOnCopyComponent.cs" />
<Compile Include="Components\RelateOnTrashComponent.cs" />
<Compile Include="StringExtensions.cs" />
<Compile Include="Strings\CleanStringType.cs" />
<Compile Include="Strings\ContentBaseExtensions.cs" />
@@ -1531,6 +1486,8 @@
<ItemGroup>
<EmbeddedResource Include="FileResources\Files.resx" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Auditing\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -4,10 +4,9 @@ using System.Threading;
using System.Web;
using System.Web.Hosting;
using LightInject;
using Serilog;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using ILogger = Umbraco.Core.Logging.ILogger;
using Umbraco.Core.Logging.Serilog;
namespace Umbraco.Core
{
@@ -28,7 +27,7 @@ namespace Umbraco.Core
/// </summary>
protected virtual ILogger GetLogger()
{
return Logger.CreateWithDefaultConfiguration();
return SerilogLogger.CreateWithDefaultConfiguration();
}
// events - in the order they trigger
@@ -161,13 +160,6 @@ namespace Umbraco.Core
_runtime = null;
}
// dispose the container and everything
// but first, capture the looger!
var logger = Current.Logger;
Current.Reset();
if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted) return;
// try to log the detailed shutdown message (typical asp.net hack: http://weblogs.asp.net/scottgu/433194)
try
{
@@ -185,7 +177,7 @@ namespace Umbraco.Core
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField,
null, runtime, null);
logger.Info<UmbracoApplicationBase>("Application shutdown. Details: {ShutdownReason}\r\n\r\n_shutDownMessage={ShutdownMessage}\r\n\r\n_shutDownStack={ShutdownStack}",
Current.Logger.Info<UmbracoApplicationBase>("Application shutdown. Details: {ShutdownReason}\r\n\r\n_shutDownMessage={ShutdownMessage}\r\n\r\n_shutDownStack={ShutdownStack}",
HostingEnvironment.ShutdownReason,
shutDownMessage,
shutDownStack);
@@ -193,20 +185,19 @@ namespace Umbraco.Core
catch (Exception)
{
//if for some reason that fails, then log the normal output
logger.Info<UmbracoApplicationBase>("Application shutdown. Reason: {ShutdownReason}", HostingEnvironment.ShutdownReason);
Current.Logger.Info<UmbracoApplicationBase>("Application shutdown. Reason: {ShutdownReason}", HostingEnvironment.ShutdownReason);
}
// dispose the container and everything
Current.Reset();
}
// called by ASP.NET (auto event wireup) once per app domain
// sender is System.Web.HttpApplicationFactory, evargs is EventArgs.Empty
protected void Application_End(object sender, EventArgs evargs)
{
HandleApplicationEnd();
OnApplicationEnd(sender, evargs);
//Not sure if we need to do this - as my POC approach I never had to deal with this
//As the LightInject container when tearing down will dispose of Serilog AFAIK
Log.CloseAndFlush();
HandleApplicationEnd();
}
#endregion

View File

@@ -50,7 +50,7 @@
<!-- note: NuGet deals with transitive references now -->
<PackageReference Include="Examine" Version="1.0.0-beta025" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NPoco" Version="3.9.3" />
<PackageReference Include="NPoco" Version="3.9.4" />
</ItemGroup>
<ItemGroup>
<Compile Include="Config\ConfigIndexCriteria.cs" />

View File

@@ -62,18 +62,16 @@
<PackageReference Include="BenchmarkDotNet.Toolchains.Roslyn" Version="0.10.13" />
<PackageReference Include="Castle.Core" Version="4.2.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="2.8.0" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.11" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="2.0.4" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="NPoco" Version="3.9.3" />
<PackageReference Include="NPoco" Version="3.9.4" />
<PackageReference Include="Umbraco.SqlServerCE" Version="4.0.0.1" />
<PackageReference Include="System.AppContext" Version="4.3.0" />
<PackageReference Include="System.Collections" Version="4.3.0" />
<PackageReference Include="System.Collections.Concurrent" Version="4.3.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.Console" Version="4.3.0" />
<PackageReference Include="System.Diagnostics.Debug" Version="4.3.0" />
<PackageReference Include="System.Diagnostics.FileVersionInfo" Version="4.3.0" />
@@ -87,7 +85,6 @@
<PackageReference Include="System.Linq" Version="4.3.0" />
<PackageReference Include="System.Linq.Expressions" Version="4.3.0" />
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="System.Reflection.Metadata" Version="1.5.0" />
<PackageReference Include="System.Reflection.Primitives" Version="4.3.0" />
<PackageReference Include="System.Resources.ResourceManager" Version="4.3.0" />
<PackageReference Include="System.Runtime" Version="4.3.0" />

View File

@@ -7,11 +7,11 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.CodeAnalysis" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.8.0.0" newVersion="2.8.0.0"/>
<bindingRedirect oldVersion="0.0.0.0-2.9.0.0" newVersion="2.9.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.CodeAnalysis.CSharp" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.8.0.0" newVersion="2.8.0.0"/>
<bindingRedirect oldVersion="0.0.0.0-2.9.0.0" newVersion="2.9.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Diagnostics.Tracing.TraceEvent" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
@@ -55,7 +55,7 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-1.2.2.0" newVersion="1.2.2.0"/>
<bindingRedirect oldVersion="0.0.0.0-1.2.3.0" newVersion="1.2.3.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ComponentModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
@@ -167,7 +167,7 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-1.4.2.0" newVersion="1.4.2.0"/>
<bindingRedirect oldVersion="0.0.0.0-1.4.3.0" newVersion="1.4.3.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
@@ -267,7 +267,7 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral"/>

View File

@@ -23,6 +23,12 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
Assert.IsTrue(SettingsSection.WebRouting.DisableAlternativeTemplates == false);
}
[Test]
public void ValidateAlternativeTemplates()
{
Assert.IsTrue(SettingsSection.WebRouting.ValidateAlternativeTemplates == false);
}
[Test]
public void DisableFindContentByIdPath()
{

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using NUnit.Framework;
using Umbraco.Core.Macros;
using Umbraco.Web.Macros;
namespace Umbraco.Tests.Macros
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Web;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;

View File

@@ -29,7 +29,7 @@ namespace Umbraco.Tests.Models
// reference, so static ctor runs, so event handlers register
// and then, this will reset the width, height... because the file does not exist, of course ;-(
var ignored = new FileUploadPropertyEditor(Mock.Of<ILogger>(), new MediaFileSystem(Mock.Of<IFileSystem>()));
var ignored = new FileUploadPropertyEditor(Mock.Of<ILogger>(), new MediaFileSystem(Mock.Of<IFileSystem>()), Mock.Of<IContentSection>());
var media = MockedMedia.CreateMediaImage(mediaType, -1);
media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests

View File

@@ -430,14 +430,15 @@ namespace Umbraco.Tests.Models
Assert.IsTrue(content.IsCultureAvailable(langUk));
Assert.IsFalse(content.IsCulturePublished(langUk));
Assert.IsNull(content.GetPublishName(langUk));
Assert.IsNull(content.GetPublishDate(langUk)); // not published
Assert.IsTrue(content.IsCultureEdited(langEs)); // not published, so... edited
Assert.IsNull(content.GetPublishDate(langUk)); // not published
Assert.IsFalse(content.IsCultureAvailable(langEs));
Assert.IsFalse(content.IsCultureEdited(langEs)); // not avail, so... not edited
Assert.IsFalse(content.IsCulturePublished(langEs));
// not published!
Assert.IsNull(content.GetPublishName(langEs));
Assert.IsNull(content.GetPublishDate(langEs)); // not published!
Assert.IsTrue(content.IsCultureEdited(langEs)); // not published, so... edited
Assert.IsNull(content.GetPublishDate(langEs));
// cannot test IsCultureEdited here - as that requires the content service and repository
// see: ContentServiceTests.Can_SaveRead_Variations

View File

@@ -45,7 +45,7 @@ namespace Umbraco.Tests.Persistence.Repositories
// Assert
Assert.That(language, Is.Not.Null);
Assert.That(language.HasIdentity, Is.True);
Assert.That(language.CultureName, Is.EqualTo("en-US"));
Assert.That(language.CultureName, Is.EqualTo("English (United States)"));
Assert.That(language.IsoCode, Is.EqualTo("en-US"));
}
}
@@ -222,7 +222,7 @@ namespace Umbraco.Tests.Persistence.Repositories
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var languageBR = (ILanguage)new Language("pt-BR") { CultureName = "pt-BR", IsDefaultVariantLanguage = true, Mandatory = true };
repository.Save(languageBR);
var languageEN = new Language("en-AU") { CultureName = "en-AU" };

View File

@@ -15,6 +15,7 @@ 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.TestHelpers;
using Umbraco.Web.Models;
using Umbraco.Web;
@@ -77,7 +78,7 @@ namespace Umbraco.Tests.PropertyEditors
var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>());
var dataTypeService = new TestObjects.TestDataTypeService(
new DataType(new ImageCropperPropertyEditor(Mock.Of<ILogger>(), mediaFileSystem, Mock.Of<IContentSection>())) { Id = 1 });
new DataType(new ImageCropperPropertyEditor(Mock.Of<ILogger>(), mediaFileSystem, Mock.Of<IContentSection>(), Mock.Of<IDataTypeService>())) { Id = 1 });
var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);

View File

@@ -14,7 +14,7 @@ using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Security;
using Umbraco.Web.Security.Identity;
namespace Umbraco.Tests.Security
{

View File

@@ -2624,9 +2624,9 @@ namespace Umbraco.Tests.Services
AssertPerCulture(content, (x, c) => x.IsCulturePublished(c), (langFr, false), (langUk, false), (langDe, false));
AssertPerCulture(content2, (x, c) => x.IsCulturePublished(c), (langFr, false), (langUk, false), (langDe, false));
// not published => must be edited
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
// not published => must be edited, if available
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
// act
@@ -2666,8 +2666,8 @@ namespace Umbraco.Tests.Services
AssertPerCulture(content2, (x, c) => x.IsCulturePublished(c), (langFr, true), (langUk, true), (langDe, false));
// fr and uk, published without changes, not edited
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, false), (langUk, false), (langDe, true));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, false), (langUk, false), (langDe, true));
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, false), (langUk, false), (langDe, false));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, false), (langUk, false), (langDe, false));
AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw
AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw
@@ -2726,8 +2726,8 @@ namespace Umbraco.Tests.Services
AssertPerCulture(content2, (x, c) => x.IsCulturePublished(c), (langFr, true), (langUk, true), (langDe, false));
// we have changed values so now fr and uk are edited
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw
AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw
@@ -2770,8 +2770,8 @@ namespace Umbraco.Tests.Services
AssertPerCulture(content2, (x, c) => x.IsCulturePublished(c), (langFr, false), (langUk, true), (langDe, false));
// and so, fr has to be edited
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw
AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw
@@ -2817,8 +2817,8 @@ namespace Umbraco.Tests.Services
AssertPerCulture(content2, (x, c) => x.IsCulturePublished(c), (langFr, false), (langUk, true), (langDe, false));
// and so, fr has to be edited - uk still is
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw
AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw
@@ -2859,8 +2859,8 @@ namespace Umbraco.Tests.Services
AssertPerCulture(content2, (x, c) => x.IsCulturePublished(c), (langFr, false), (langUk, true), (langDe, false));
// no change, back to published
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true));
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, false));
AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw
AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw
@@ -2882,8 +2882,8 @@ namespace Umbraco.Tests.Services
AssertPerCulture(content2, (x, c) => x.IsCulturePublished(c), (langFr, false), (langUk, true), (langDe, false));
// now, uk is no more edited
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, false), (langDe, true));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, false), (langDe, true));
AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, false), (langDe, false));
AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, false), (langDe, false));
AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw
AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw

View File

@@ -5,6 +5,9 @@ namespace Umbraco.Tests.TestHelpers
{
public class ConsoleLogger : ILogger
{
public bool IsEnabled(Type reporting, LogLevel level)
=> true;
public void Fatal(Type reporting, Exception exception, string message)
{
Console.WriteLine("FATAL {0} - {1}", reporting.Name, message);
@@ -22,15 +25,15 @@ namespace Umbraco.Tests.TestHelpers
Console.WriteLine("FATAL {0} - {1}", reporting.Name, message);
}
public void Fatal(Type reporting, Exception exception, string format, params object[] args)
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
Console.WriteLine("FATAL {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("FATAL {0} - {1}", reporting.Name, MessageTemplates.Render(messageTemplate, propertyValues));
Console.WriteLine(exception);
}
public void Fatal(Type reporting, string format, params object[] args)
public void Fatal(Type reporting, string messageTemplate, params object[] propertyValues)
{
Console.WriteLine("FATAL {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("FATAL {0} - {1}", reporting.Name, MessageTemplates.Render(messageTemplate, propertyValues));
}
public void Error(Type reporting, Exception exception, string message)
@@ -50,15 +53,15 @@ namespace Umbraco.Tests.TestHelpers
Console.WriteLine("ERROR {0} - {1}", reporting.Name, message);
}
public void Error(Type reporting, Exception exception, string format, params object[] args)
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
{
Console.WriteLine("ERROR {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("ERROR {0} - {1}", reporting.Name, MessageTemplates.Render(messageTemplate, propertyValues));
Console.WriteLine(exception);
}
public void Error(Type reporting, string format, params object[] args)
public void Error(Type reporting, string messageTemplate, params object[] propertyValues)
{
Console.WriteLine("ERROR {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("ERROR {0} - {1}", reporting.Name, MessageTemplates.Render(messageTemplate, propertyValues));
}
public void Warn(Type reporting, string message)
@@ -66,9 +69,9 @@ namespace Umbraco.Tests.TestHelpers
Console.WriteLine("WARN {0} - {1}", reporting.Name, message);
}
public void Warn(Type reporting, string format, params object[] args)
public void Warn(Type reporting, string message, params object[] propertyValues)
{
Console.WriteLine("WARN {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("WARN {0} - {1}", reporting.Name, MessageTemplates.Render(message, propertyValues));
}
public void Warn(Type reporting, Exception exception, string message)
@@ -76,16 +79,16 @@ namespace Umbraco.Tests.TestHelpers
Console.WriteLine("WARN {0} - {1}", reporting.Name, message);
Console.WriteLine(exception);
}
public void Warn(Type reporting, Exception exception, string format, params object[] args)
public void Warn(Type reporting, Exception exception, string message, params object[] propertyValues)
{
Console.WriteLine("WARN {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("WARN {0} - {1}", reporting.Name, MessageTemplates.Render(message, propertyValues));
Console.WriteLine(exception);
}
public void Info(Type reporting, string format, params object[] args)
public void Info(Type reporting, string messageTemplate, params object[] propertyValues)
{
Console.WriteLine("INFO {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("INFO {0} - {1}", reporting.Name, MessageTemplates.Render(messageTemplate, propertyValues));
}
public void Info(Type reporting, string message)
@@ -98,9 +101,9 @@ namespace Umbraco.Tests.TestHelpers
Console.WriteLine("DEBUG {0} - {1}", reporting.Name, message);
}
public void Debug(Type reporting, string format, params object[] args)
public void Debug(Type reporting, string messageTemplate, params object[] propertyValues)
{
Console.WriteLine("DEBUG {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("DEBUG {0} - {1}", reporting.Name, MessageTemplates.Render(messageTemplate, propertyValues));
}
public void Verbose(Type reporting, string message)
@@ -108,9 +111,9 @@ namespace Umbraco.Tests.TestHelpers
Console.WriteLine("VERBOSE {0} - {1}", reporting.Name, message);
}
public void Verbose(Type reporting, string format, params object[] args)
public void Verbose(Type reporting, string messageTemplate, params object[] propertyValues)
{
Console.WriteLine("VERBOSE {0} - {1}", reporting.Name, string.Format(format, args));
Console.WriteLine("VERBOSE {0} - {1}", reporting.Name, MessageTemplates.Render(messageTemplate, propertyValues));
}
}
}

View File

@@ -96,7 +96,11 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
var httpContext = Mock.Of<HttpContextBase>(
http => http.User == owinContext.Authentication.User
//ensure the request exists with a cookies collection
&& http.Request == Mock.Of<HttpRequestBase>(r => r.Cookies == new HttpCookieCollection())
&& http.Request == Mock.Of<HttpRequestBase>(r => r.Cookies == new HttpCookieCollection()
&& r.RequestContext == new System.Web.Routing.RequestContext
{
RouteData = new System.Web.Routing.RouteData()
})
//ensure the request exists with an items collection
&& http.Items == httpContextItems);
//chuck it into the props since this is what MS does when hosted and it's needed there

View File

@@ -58,14 +58,14 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
var response = await server.HttpClient.SendAsync(request);
Console.WriteLine(response);
var json = "";
if (response.IsSuccessStatusCode == false)
{
WriteResponseError(response);
}
else
var json = (await ((StreamContent)response.Content).ReadAsStringAsync()).TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix);
if (!json.IsNullOrWhiteSpace())
{
json = (await ((StreamContent) response.Content).ReadAsStringAsync()).TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix);
var deserialized = JsonConvert.DeserializeObject(json);
Console.Write(JsonConvert.SerializeObject(deserialized, Formatting.Indented));
}

View File

@@ -127,6 +127,11 @@ namespace Umbraco.Tests.TestHelpers
{
if (!(expected is string) && expected is IEnumerable)
{
// sort property collection by alias, not by property ids
// on members, built-in properties don't have ids (always zero)
if (expected is PropertyCollection)
sorter = e => ((PropertyCollection) e).OrderBy(x => x.Alias);
// compare lists
AssertListsAreEqual(property, (IEnumerable) actual, (IEnumerable) expected, sorter, dateDeltaMilliseconds);
}
@@ -168,6 +173,8 @@ namespace Umbraco.Tests.TestHelpers
private static void AssertListsAreEqual(PropertyInfo property, IEnumerable expected, IEnumerable actual, Func<IEnumerable, IEnumerable> sorter = null, int dateDeltaMilliseconds = 0)
{
if (sorter == null)
{
// this is pretty hackerific but saves us some code to write

View File

@@ -17,6 +17,7 @@ using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.IO.MediaPathSchemes;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging.Serilog;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence;
@@ -167,7 +168,7 @@ namespace Umbraco.Tests.Testing
}
else if (option == UmbracoTestOptions.Logger.Serilog)
{
Container.RegisterSingleton<ILogger>(f => new Logger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config"))));
Container.RegisterSingleton<ILogger>(f => new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config"))));
Container.RegisterSingleton<IProfiler>(f => new LogProfiler(f.GetInstance<ILogger>()));
}

View File

@@ -82,7 +82,7 @@
<PackageReference Include="LightInject.Annotation" Version="1.1.0" />
<PackageReference Include="Lucene.Net" Version="3.0.3" />
<PackageReference Include="Lucene.Net.Contrib" Version="3.0.3" />
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.1" />
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNet.Mvc" Version="5.2.6" />
<PackageReference Include="Microsoft.AspNet.WebApi" Version="5.2.6" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.6" />
@@ -96,7 +96,7 @@
<PackageReference Include="Microsoft.Web.Infrastructure" Version="1.0.0.0" />
<PackageReference Include="MiniProfiler" Version="3.2.0.157" />
<PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="NPoco" Version="3.9.3" />
<PackageReference Include="NPoco" Version="3.9.4" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />

View File

@@ -1,15 +1,10 @@
using Moq;
using System.IO;
using LightInject;
using System.IO;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Profiling;
using Umbraco.Core.Logging.Serilog;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
using Umbraco.Examine;
namespace Umbraco.Tests.UmbracoExamine
{
@@ -19,18 +14,12 @@ namespace Umbraco.Tests.UmbracoExamine
[OneTimeSetUp]
public void InitializeFixture()
{
var logger = new Logger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config")));
var logger = new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config")));
_profilingLogger = new ProfilingLogger(logger, new LogProfiler(logger));
}
private ProfilingLogger _profilingLogger;
protected override ProfilingLogger ProfilingLogger
{
get
{
return _profilingLogger;
}
}
protected override ProfilingLogger ProfilingLogger => _profilingLogger;
/// <summary>
/// sets up resolvers before resolution is frozen

View File

@@ -24,6 +24,12 @@ using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web._Legacy.Actions;
using Task = System.Threading.Tasks.Task;
using Umbraco.Core.Dictionary;
using Umbraco.Web.PropertyEditors;
using System;
using Umbraco.Web.WebApi;
using Umbraco.Web.Trees;
using System.Globalization;
namespace Umbraco.Tests.Web.Controllers
{
@@ -40,6 +46,8 @@ namespace Umbraco.Tests.Web.Controllers
var userServiceMock = new Mock<IUserService>();
userServiceMock.Setup(service => service.GetUserById(It.IsAny<int>()))
.Returns((int id) => id == 1234 ? new User(1234, "Test", "test@test.com", "test@test.com", "", new List<IReadOnlyUserGroup>(), new int[0], new int[0]) : null);
userServiceMock.Setup(x => x.GetProfileById(It.IsAny<int>()))
.Returns((int id) => id == 1234 ? new User(1234, "Test", "test@test.com", "test@test.com", "", new List<IReadOnlyUserGroup>(), new int[0], new int[0]) : null);
userServiceMock.Setup(service => service.GetPermissionsForPath(It.IsAny<IUser>(), It.IsAny<string>()))
.Returns(new EntityPermissionSet(123, new EntityPermissionCollection(new[]
{
@@ -54,23 +62,34 @@ namespace Umbraco.Tests.Web.Controllers
var entityService = new Mock<IEntityService>();
entityService.Setup(x => x.GetAllPaths(UmbracoObjectTypes.Document, It.IsAny<int[]>()))
.Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath {Path = $"-1,{x}", Id = x}).ToList());
.Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = $"-1,{x}", Id = x }).ToList());
var dataTypeService = new Mock<IDataTypeService>();
dataTypeService.Setup(service => service.GetDataType(It.IsAny<int>()))
.Returns(MockedDataType());
.Returns(Mock.Of<IDataType>(type => type.Id == 9876 && type.Name == "text"));
dataTypeService.Setup(service => service.GetDataType(-87)) //the RTE
.Returns(Mock.Of<IDataType>(type => type.Id == -87 && type.Name == "Rich text" && type.Configuration == new RichTextConfiguration()));
var langService = new Mock<ILocalizationService>();
langService.Setup(x => x.GetAllLanguages()).Returns(new[] {
Mock.Of<ILanguage>(x => x.IsoCode == "en-US"),
Mock.Of<ILanguage>(x => x.IsoCode == "es-ES"),
Mock.Of<ILanguage>(x => x.IsoCode == "fr-FR")
});
var textService = new Mock<ILocalizedTextService>();
textService.Setup(x => x.Localize(It.IsAny<string>(), It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>())).Returns("");
Container.RegisterSingleton(f => Mock.Of<IContentService>());
Container.RegisterSingleton(f => userServiceMock.Object);
Container.RegisterSingleton(f => entityService.Object);
Container.RegisterSingleton(f => dataTypeService.Object);
Container.RegisterSingleton(f => langService.Object);
Container.RegisterSingleton(f => textService.Object);
Container.RegisterSingleton(f => Mock.Of<ICultureDictionaryFactory>());
Container.RegisterSingleton(f => new UmbracoApiControllerTypeCollection(new[] { typeof(ContentTreeController) }));
}
private IDataType MockedDataType()
{
return Mock.Of<IDataType>(type => type.Id == 9876 && type.Name == "text");
}
private MultipartFormDataContent GetMultiPartRequestContent(string json)
{
var multiPartBoundary = "----WebKitFormBoundary123456789";
@@ -89,14 +108,34 @@ namespace Umbraco.Tests.Web.Controllers
};
}
private const string PublishJson1 = @"{
private IContent GetMockedContent()
{
var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType());
content.Id = 123;
content.Path = "-1,123";
//ensure things have ids
var ids = 888;
foreach (var g in content.PropertyGroups)
{
g.Id = ids;
ids++;
}
foreach (var p in content.PropertyTypes)
{
p.Id = ids;
ids++;
}
return content;
}
private const string PublishJsonInvariant = @"{
""id"": 123,
""contentTypeAlias"": ""page"",
""parentId"": -1,
""action"": ""save"",
""variants"": [
{
""name"": null,
""name"": ""asdf"",
""properties"": [
{
""id"": 1,
@@ -104,10 +143,34 @@ namespace Umbraco.Tests.Web.Controllers
""value"": ""asdf""
}
],
""culture"": ""en-US""
""culture"": null,
""save"": true,
""publish"": true
}
]
}";
private const string PublishJsonVariant = @"{
""id"": 123,
""contentTypeAlias"": ""page"",
""parentId"": -1,
""action"": ""save"",
""variants"": [
{
""name"": ""asdf"",
""properties"": [
{
""id"": 1,
""alias"": ""title"",
""value"": ""asdf""
}
],
""culture"": ""en-US"",
""save"": true,
""publish"": true
},
{
""name"": null,
""name"": ""asdf"",
""properties"": [
{
""id"": 1,
@@ -115,7 +178,9 @@ namespace Umbraco.Tests.Web.Controllers
""value"": ""asdf""
}
],
""culture"": ""fr-FR""
""culture"": ""fr-FR"",
""save"": true,
""publish"": true
},
{
""name"": ""asdf"",
@@ -142,12 +207,8 @@ namespace Umbraco.Tests.Web.Controllers
{
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
{
//var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType());
//content.Id = 999999999; //this will not be found
//content.Path = "-1,999999999";
var contentServiceMock = Mock.Get(Current.Services.ContentService);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null); //do not find it
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
@@ -158,7 +219,7 @@ namespace Umbraco.Tests.Web.Controllers
var runner = new TestRunner(Factory);
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
content: GetMultiPartRequestContent(PublishJson1),
content: GetMultiPartRequestContent(PublishJsonInvariant),
mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"),
assertOkResponse: false);
@@ -175,7 +236,7 @@ namespace Umbraco.Tests.Web.Controllers
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
{
var contentServiceMock = Mock.Get(Current.Services.ContentService);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => GetMockedContent());
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
@@ -184,9 +245,9 @@ namespace Umbraco.Tests.Web.Controllers
return usersController;
}
var json = JsonConvert.DeserializeObject<JObject>(PublishJson1);
var json = JsonConvert.DeserializeObject<JObject>(PublishJsonInvariant);
//remove all save flaggs
((JArray)json["variants"])[2]["save"] = false;
((JArray)json["variants"])[0]["save"] = false;
var runner = new TestRunner(Factory);
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
@@ -202,20 +263,52 @@ namespace Umbraco.Tests.Web.Controllers
/// Returns 404 if any of the posted properties dont actually exist
/// </summary>
/// <returns></returns>
[Test, Ignore("Not implemented yet")]
[Test]
public async Task PostSave_Validate_Properties_Exist()
{
//TODO: Make this work! to finish it, we need to include a property in the POST data that doesn't exist on the content type
// or change the content type below to not include one of the posted ones
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
{
var contentServiceMock = Mock.Get(Current.Services.ContentService);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => GetMockedContent());
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
var usersController = new ContentController(publishedSnapshot, propertyEditorCollection);
Container.InjectProperties(usersController);
return usersController;
}
var json = JsonConvert.DeserializeObject<JObject>(PublishJsonInvariant);
//add a non-existent property to a variant being saved
var variantProps = (JArray)json["variants"].ElementAt(0)["properties"];
variantProps.Add(JObject.FromObject(new
{
id = 2,
alias = "doesntExist",
value = "hello"
}));
var runner = new TestRunner(Factory);
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)),
mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"),
assertOkResponse: false);
Assert.AreEqual(HttpStatusCode.NotFound, response.Item1.StatusCode);
}
[Test]
public async Task PostSave_Simple_Invariant()
{
var content = GetMockedContent();
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
{
var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType());
content.Id = 123;
content.Path = "-1,123";
var contentServiceMock = Mock.Get(Current.Services.ContentService);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content);
contentServiceMock.Setup(x => x.Save(It.IsAny<IContent>(), It.IsAny<int>(), It.IsAny<bool>()))
.Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
@@ -226,14 +319,92 @@ namespace Umbraco.Tests.Web.Controllers
var runner = new TestRunner(Factory);
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
content: GetMultiPartRequestContent(PublishJson1),
content: GetMultiPartRequestContent(PublishJsonInvariant),
mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"),
assertOkResponse: false);
Assert.AreEqual(HttpStatusCode.NotFound, response.Item1.StatusCode);
//var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(response.Item2);
//Assert.AreEqual(0, obj.TotalItems);
Assert.AreEqual(HttpStatusCode.OK, response.Item1.StatusCode);
var display = JsonConvert.DeserializeObject<ContentItemDisplay>(response.Item2);
Assert.AreEqual(1, display.Variants.Count());
Assert.AreEqual(content.PropertyGroups.Count(), display.Variants.ElementAt(0).Tabs.Count());
Assert.AreEqual(content.PropertyTypes.Count(), display.Variants.ElementAt(0).Tabs.ElementAt(0).Properties.Count());
}
[Test]
public async Task PostSave_Validate_Empty_Name()
{
var content = GetMockedContent();
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
{
var contentServiceMock = Mock.Get(Current.Services.ContentService);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content);
contentServiceMock.Setup(x => x.Save(It.IsAny<IContent>(), It.IsAny<int>(), It.IsAny<bool>()))
.Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
var usersController = new ContentController(publishedSnapshot, propertyEditorCollection);
Container.InjectProperties(usersController);
return usersController;
}
//clear out the name
var json = JsonConvert.DeserializeObject<JObject>(PublishJsonInvariant);
json["variants"].ElementAt(0)["name"] = null;
var runner = new TestRunner(Factory);
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)),
mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"),
assertOkResponse: false);
Assert.AreEqual(HttpStatusCode.BadRequest, response.Item1.StatusCode);
var display = JsonConvert.DeserializeObject<ContentItemDisplay>(response.Item2);
Assert.AreEqual(1, display.Errors.Count());
Assert.IsTrue(display.Errors.ContainsKey("Variants[0].Name"));
//ModelState":{"Variants[0].Name":["Required"]}
}
[Test]
public async Task PostSave_Validate_Variants_Empty_Name()
{
var content = GetMockedContent();
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
{
var contentServiceMock = Mock.Get(Current.Services.ContentService);
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => content);
contentServiceMock.Setup(x => x.Save(It.IsAny<IContent>(), It.IsAny<int>(), It.IsAny<bool>()))
.Returns(new OperationResult(OperationResultType.Success, new Core.Events.EventMessages())); //success
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
var usersController = new ContentController(publishedSnapshot, propertyEditorCollection);
Container.InjectProperties(usersController);
return usersController;
}
//clear out one of the names
var json = JsonConvert.DeserializeObject<JObject>(PublishJsonVariant);
json["variants"].ElementAt(0)["name"] = null;
var runner = new TestRunner(Factory);
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)),
mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"),
assertOkResponse: false);
Assert.AreEqual(HttpStatusCode.BadRequest, response.Item1.StatusCode);
var display = JsonConvert.DeserializeObject<ContentItemDisplay>(response.Item2);
Assert.AreEqual(2, display.Errors.Count());
Assert.IsTrue(display.Errors.ContainsKey("Variants[0].Name"));
Assert.IsTrue(display.Errors.ContainsKey("_content_variant_en-US_"));
}
//TODO: There are SOOOOO many more tests we should write - a lot of them to do with validation
}
}

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Web.Mvc
[Test]
public void ReplaceLineBreaksWithHtmlBreak()
{
var output = _htmlStringUtilities.ReplaceLineBreaksForHtml("<div><h1>hello world</h1><p>hello world\r\nhello world\rhello world\nhello world</p></div>");
var output = _htmlStringUtilities.ReplaceLineBreaksForHtml("<div><h1>hello world</h1><p>hello world\r\nhello world\rhello world\nhello world</p></div>").ToString();
var expected = "<div><h1>hello world</h1><p>hello world<br />hello world<br />hello world<br />hello world</p></div>";
Assert.AreEqual(expected, output);
}
@@ -58,4 +58,4 @@ namespace Umbraco.Tests.Web.Mvc
}
}
}

View File

@@ -1 +0,0 @@
src/common/services/util.service.js

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
</staticContent>
</system.webServer>
</configuration>

View File

@@ -455,13 +455,28 @@ In the following example you see how to run some custom logic before a step goes
function waitForPendingRerequests() {
var deferred = $q.defer();
var timer = window.setInterval(function(){
var requestsReady = false;
var animationsDone = false;
// check for pending requests both in angular and on the document
if($http.pendingRequests.length === 0 && document.readyState === "complete") {
requestsReady = true;
}
// check for animations. ng-enter and ng-leave are default angular animations.
// Also check for infinite editors animating
if(document.querySelectorAll(".ng-enter, .ng-leave, .umb-editor--animating").length === 0) {
animationsDone = true;
}
if(requestsReady && animationsDone) {
$timeout(function(){
deferred.resolve();
clearInterval(timer);
});
}
}, 50);
return deferred.promise;
}

View File

@@ -25,11 +25,6 @@
$scope.page.hideChangeVariant = infiniteMode ? true : false;
$scope.allowOpen = true;
// add all editors to an editors array to support split view
$scope.editors = [];
$scope.initVariant = initVariant;
$scope.splitViewChanged = splitViewChanged;
function init(content) {
if (infiniteMode) {
@@ -55,17 +50,10 @@
// set first app to active
// We need to track active
$scope.content.apps[0].active = true;
setActiveCulture();
resetVariantFlags();
}
/** This is called when the split view changes based on the umb-variant-content */
function splitViewChanged() {
//send an event downwards
$scope.$broadcast("editors.content.splitViewChanged", { editors: $scope.editors });
}
/**
* This will reset isDirty flags if save is true.
@@ -91,123 +79,12 @@
$scope.content.variants[0].publish = false;
}
}
function countDirtyVariants() {
var count = 0;
for (var i = 0; i < $scope.content.variants.length; i++) {
var v = $scope.content.variants[i];
if (v.isDirty) {
count++;
}
}
return count;
}
/** Returns true if the save/publish dialog should be shown when pressing the button */
function showSaveOrPublishDialog() {
return $scope.content.variants.length > 1;
}
/**
* The content item(s) are loaded into an array and this will set the active content item based on the current culture (query string).
* If the content item is invariant, then only one item exists in the array.
*/
function setActiveCulture() {
// set the active variant
var activeVariant = null;
_.each($scope.content.variants, function (v) {
if (v.language && v.language.culture === $scope.culture) {
v.active = true;
activeVariant = v;
}
else {
v.active = false;
}
});
if (!activeVariant) {
// set the first variant to active
$scope.content.variants[0].active = true;
activeVariant = $scope.content.variants[0];
}
initVariant(activeVariant);
//If there are no editors yet then create one with the current content.
//if there's already a main editor then update it with the current content.
if ($scope.editors.length === 0) {
var editor = {
content: activeVariant
};
$scope.editors.push(editor);
}
else {
//this will mean there is only one
$scope.editors[0].content = activeVariant;
if ($scope.editors.length > 1) {
//now re-sync any other editor content (i.e. if split view is open)
for (var s = 1; s < $scope.editors.length; s++) {
//get the variant from the scope model
var variant = _.find($scope.content.variants, function (v) {
return v.language.culture === $scope.editors[s].content.language.culture;
});
$scope.editors[s].content = initVariant(variant);
}
}
}
}
function initVariant(variant) {
//The model that is assigned to the editor contains the current content variant along
//with a copy of the contentApps. This is required because each editor renders it's own
//header and content apps section and the content apps contains the view for editing content itself
//and we need to assign a view model to the subView so that it is scoped to the current
//editor so that split views work.
//copy the apps from the main model if not assigned yet to the variant
if (!variant.apps) {
variant.apps = angular.copy($scope.content.apps);
}
//if this is a variant has a culture/language than we need to assign the language drop down info
if (variant.language) {
//if the variant list that defines the header drop down isn't assigned to the variant then assign it now
if (!variant.variants) {
variant.variants = _.map($scope.content.variants,
function (v) {
return _.pick(v, "active", "language", "state");
});
}
else {
//merge the scope variants on top of the header variants collection (handy when needing to refresh)
angular.extend(variant.variants,
_.map($scope.content.variants,
function (v) {
return _.pick(v, "active", "language", "state");
}));
}
//ensure the current culture is set as the active one
for (var i = 0; i < variant.variants.length; i++) {
if (variant.variants[i].language.culture === variant.language.culture) {
variant.variants[i].active = true;
}
else {
variant.variants[i].active = false;
}
}
}
//then assign the variant to a view model to the content app
var contentApp = _.find(variant.apps, function (a) {
return a.alias === "content";
});
contentApp.viewModel = variant;
return variant;
}
function bindEvents() {
//bindEvents can be called more than once and we don't want to have multiple bound events
for (var e in evts) {
@@ -249,9 +126,7 @@
init($scope.content);
if (!infiniteMode) {
syncTreeNode($scope.content, true);
}
syncTreeNode($scope.content, $scope.content.path, true);
resetLastListPageNumber($scope.content);
@@ -300,10 +175,12 @@
}
/** Syncs the content item to it's tree node - this occurs on first load and after saving */
function syncTreeNode(content, initialLoad) {
var path = content.path;
function syncTreeNode(content, path, initialLoad) {
if (infiniteMode || !path) {
return;
}
if (!$scope.content.isChildOfListView) {
navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
$scope.page.menu.currentNode = syncArgs.node;
@@ -341,9 +218,7 @@
//success
init($scope.content);
if (!infiniteMode) {
syncTreeNode($scope.content);
}
syncTreeNode($scope.content, data.path);
$scope.page.buttonGroupState = "success";
@@ -353,8 +228,7 @@
},
function (err) {
setActiveCulture();
syncTreeNode($scope.content);
syncTreeNode($scope.content, $scope.content.path);
//error
if (err) {
@@ -367,6 +241,19 @@
});
}
function clearNotifications(content) {
if (content.notifications) {
content.notifications = [];
}
if (content.variants) {
for (var i = 0; i < content.variants.length; i++) {
if (content.variants[i].notifications) {
content.variants[i].notifications = [];
}
}
}
}
function resetLastListPageNumber(content) {
// We're using rootScope to store the page number for list views, so if returning to the list
// we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children
@@ -376,6 +263,24 @@
}
}
/**
* Used to clear the dirty state for successfully saved variants when not all variant saving was successful
* @param {any} variants
*/
function clearDirtyState(variants) {
for (var i = 0; i < variants.length; i++) {
var v = variants[i];
if (v.notifications) {
var isSuccess = _.find(v.notifications, function (n) {
return n.type === 3; //this is a success notification
});
if (isSuccess) {
v.isDirty = false;
}
}
}
}
if ($scope.page.isNew) {
$scope.page.loading = true;
@@ -438,9 +343,7 @@
init($scope.content);
if (!infiniteMode) {
syncTreeNode($scope.content);
}
syncTreeNode($scope.content, data.path);
$scope.page.buttonGroupState = "success";
@@ -458,35 +361,40 @@
};
$scope.saveAndPublish = function () {
clearNotifications($scope.content);
// TODO: Add "..." to publish button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
if (showSaveOrPublishDialog()) {
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
var dialog = {
parentScope: $scope,
view: "views/content/overlays/publish.html",
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabel: "Publish",
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
return performSave({
saveMethod: contentResource.publish,
action: "publish",
showNotifications: false
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
return $q.reject(err);
//don't reject, we've handled the error
return $q.when(err);
});
},
close: function (oldModel) {
@@ -505,35 +413,40 @@
};
$scope.save = function () {
clearNotifications($scope.content);
// TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
if (showSaveOrPublishDialog()) {
//before we launch the dialog we want to execute all client side validations first
if (formHelper.submitForm({ scope: $scope, action: "save" })) {
var dialog = {
parentScope: $scope,
view: "views/content/overlays/save.html",
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabel: "Save",
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
return performSave({
saveMethod: $scope.saveMethod(),
action: "save",
showNotifications: false
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
return $q.reject(err);
//don't reject, we've handled the error
return $q.when(err);
});
},
close: function (oldModel) {
@@ -576,10 +489,6 @@
}
};
$scope.backToListView = function () {
$location.path($scope.page.listViewPath);
};
$scope.restore = function (content) {
$scope.page.buttonRestore = "busy";
@@ -685,12 +594,6 @@
}
};
$scope.$watch('culture', function (newVal, oldVal) {
if (newVal !== oldVal) {
setActiveCulture();
}
});
//ensure to unregister from all events!
$scope.$on('$destroy', function () {
for (var e in evts) {

View File

@@ -13,7 +13,7 @@
scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
scope.allowChangeDocumentType = false;
scope.allowChangeTemplate = false;
function onInit() {
userService.getCurrentUser().then(function(user){
@@ -71,10 +71,11 @@
// make sure dates are formatted to the user's locale
formatDatesToLocal();
// Declare a fallback URL for the <umb-node-preview/> directive
scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id;
// Declare a fallback URL for the <umb-node-preview/> directive
if (scope.documentType !== null) {
scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id;
}
}
scope.auditTrailPageChange = function (pageNumber) {
@@ -142,7 +143,7 @@
item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL');
});
});
scope.auditTrail = data.items;
scope.auditTrailOptions.pageNumber = data.pageNumber;
scope.auditTrailOptions.pageSize = data.pageSize;
@@ -150,7 +151,7 @@
scope.auditTrailOptions.totalPages = data.totalPages;
setAuditTrailLogTypeColor(scope.auditTrail);
scope.loadingAuditTrail = false;
});
@@ -158,6 +159,7 @@
function setAuditTrailLogTypeColor(auditTrail) {
angular.forEach(auditTrail, function (item) {
switch (item.logType) {
case "Publish":
item.logTypeColor = "success";
@@ -286,7 +288,7 @@
eventsService.emit("editors.content.changeUnpublishDate", args);
}
function ucfirst(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
@@ -295,13 +297,13 @@
// get current backoffice user and format dates
userService.getCurrentUser().then(function (currentUser) {
scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL');
scope.node.releaseDateYear = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'YYYY')) : null;
scope.node.releaseDateMonth = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'MMMM')) : null;
scope.node.releaseDateDayNumber = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'DD')) : null;
scope.node.releaseDateDay = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'dddd')) : null;
scope.node.releaseDateTime = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'HH:mm')) : null;
scope.node.removeDateYear = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'YYYY')) : null;
scope.node.removeDateMonth = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'MMMM')) : null;
scope.node.removeDateDayNumber = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'DD')) : null;
@@ -326,8 +328,8 @@
scope.$watch('node.updateDate', function(newValue, oldValue){
if(!newValue) { return; }
if(newValue === oldValue) { return; }
if(newValue === oldValue) { return; }
if(isInfoTab) {
loadAuditTrail();
formatDatesToLocal();

View File

@@ -2,133 +2,79 @@
'use strict';
/**
* A directive to encapsulate each variant editor which includes the name header and all content apps for a given variant
* @param {any} $timeout
* @param {any} $location
* A component to encapsulate each variant editor which includes the name header and all content apps for a given variant
*/
function variantContentDirective($timeout, $location) {
var umbVariantContent = {
templateUrl: 'views/components/content/umb-variant-content.html',
bindings: {
content: "<",
page: "<",
editor: "<",
editorIndex: "<",
editorCount: "<",
onCloseSplitView: "&",
onSelectVariant: "&",
onOpenSplitView: "&"
},
controllerAs: 'vm',
controller: umbVariantContentController
};
function umbVariantContentController($scope, $element, $location) {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/content/umb-variant-content.html',
link: function (scope) {
var unsubscribe = [];
/**
* Adds a new editor to the editors array to show content in a split view
* @param {any} selectedVariant
*/
scope.openInSplitView = function (selectedVariant) {
var vm = this;
var selectedCulture = selectedVariant.language.culture;
vm.$postLink = postLink;
vm.$onDestroy = onDestroy;
//only the content app can be selected since no other apps are shown, and because we copy all of these apps
//to the "editors" we need to update this across all editors
for (var e = 0; e < scope.editors.length; e++) {
var editor = scope.editors[e];
for (var i = 0; i < editor.content.apps.length; i++) {
var app = editor.content.apps[i];
if (app.alias === "content") {
app.active = true;
}
else {
app.active = false;
}
}
vm.selectVariant = selectVariant;
vm.openSplitView = openSplitView;
vm.backToListView = backToListView;
/** Called when the component has linked all elements, this is when the form controller is available */
function postLink() {
//set the content to dirty if the header changes
unsubscribe.push($scope.$watch("contentHeaderForm.$dirty",
function(newValue, oldValue) {
if (newValue === true) {
vm.editor.content.isDirty = true;
}
//Find the whole variant model based on the culture that was chosen
var variant = _.find(scope.content.variants, function (v) {
return v.language.culture === selectedCulture;
});
var editor = {
content: scope.initVariant({ variant: variant})
};
scope.editors.push(editor);
//TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
editor.collapsed = true;
editor.loading = true;
$timeout(function () {
editor.collapsed = false;
editor.loading = false;
scope.onSplitViewChanged();
}, 100);
};
/**
* Changes the currently selected variant
* @param {any} variantDropDownItem
*/
scope.selectVariant = function (variantDropDownItem) {
var editorIndex = _.findIndex(scope.editors, function (e) {
return e === scope.editor;
});
//if the editor index is zero, then update the query string to track the lang selection, otherwise if it's part
//of a 2nd split view editor then update the model directly.
if (editorIndex === 0) {
//if we've made it this far, then update the query string
$location.search("cculture", variantDropDownItem.language.culture);
}
else {
//set all variant drop down items as inactive for this editor and then set the selected on as active
for (var i = 0; i < scope.editor.content.variants.length; i++) {
scope.editor.content.variants[i].active = false;
}
variantDropDownItem.active = true;
//get the variant content model and initialize the editor with that
var variant = _.find(scope.content.variants, function (v) {
return v.language.culture === variantDropDownItem.language.culture;
});
scope.editor.content = scope.initVariant({ variant: variant });
}
};
/** Closes the split view */
scope.closeSplitView = function () {
//TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
scope.editor.loading = true;
scope.editor.collapsed = true;
$timeout(function () {
var index = _.findIndex(scope.editors, function(e) {
return e === scope.editor;
});
scope.editors.splice(index, 1);
scope.onSplitViewChanged();
}, 400);
};
//set the content to dirty if the header changes
scope.$watch("contentHeaderForm.$dirty",
function (newValue, oldValue) {
if (newValue === true) {
scope.editor.content.isDirty = true;
}
});
},
scope: {
//TODO: This should be turned into a proper component
page: "=",
content: "=",
editor: "=",
editors: "=",
//TODO: I don't like having this callback defined and would like to make this directive a bit less
// coupled but right now don't have time
initVariant: "&",
onSplitViewChanged: "&"
}));
}
function onDestroy() {
for (var i = 0; i < unsubscribe.length; i++) {
unsubscribe[i]();
}
}
function backToListView() {
$location.path(vm.page.listViewPath);
};
return directive;
/**
* Used to proxy a callback
* @param {any} variant
*/
function selectVariant(variant) {
if (vm.onSelectVariant) {
vm.onSelectVariant({ "variant": variant });
}
}
/**
* Used to proxy a callback
* @param {any} variant
*/
function openSplitView(variant) {
if (vm.onOpenSplitView) {
vm.onOpenSplitView({ "variant": variant });
}
}
}
angular.module('umbraco.directives').directive('umbVariantContent', variantContentDirective);
angular.module('umbraco.directives').component('umbVariantContent', umbVariantContent);
})();

View File

@@ -0,0 +1,280 @@
(function () {
'use strict';
/**
* A component for split view content editing
*/
var umbVariantContentEditors = {
templateUrl: 'views/components/content/umb-variant-content-editors.html',
bindings: {
page: "<",
content: "<", //TODO: Not sure if this should be = since we are changing the 'active' property of a variant
culture: "<"
},
controllerAs: 'vm',
controller: umbVariantContentEditorsController
};
function umbVariantContentEditorsController($scope, $element, $location, $timeout) {
var prevContentDateUpdated = null;
var vm = this;
vm.$onInit = onInit;
vm.$onChanges = onChanges;
vm.$doCheck = doCheck;
vm.$postLink = postLink;
vm.openSplitView = openSplitView;
vm.closeSplitView = closeSplitView;
vm.selectVariant = selectVariant;
//Used to track how many content views there are (for split view there will be 2, it could support more in theory)
vm.editors = [];
/** Called when the component initializes */
function onInit() {
prevContentDateUpdated = angular.copy(vm.content.updateDate);
setActiveCulture();
}
/** Called when the component has linked all elements, this is when the form controller is available */
function postLink() {
}
/**
* Watch for model changes
* @param {any} changes
*/
function onChanges(changes) {
if (changes.culture && !changes.culture.isFirstChange() && changes.culture.currentValue !== changes.culture.previousValue) {
setActiveCulture();
}
}
/** Allows us to deep watch whatever we want - executes on every digest cycle */
function doCheck() {
if (!angular.equals(vm.content.updateDate, prevContentDateUpdated)) {
setActiveCulture();
prevContentDateUpdated = angular.copy(vm.content.updateDate);
}
}
/** This is called when the split view changes based on the umb-variant-content */
function splitViewChanged() {
//send an event downwards
$scope.$broadcast("editors.content.splitViewChanged", { editors: vm.editors });
}
/**
* Set the active variant based on the current culture (query string)
*/
function setActiveCulture() {
// set the active variant
var activeVariant = null;
_.each(vm.content.variants, function (v) {
if (v.language && v.language.culture === vm.culture) {
v.active = true;
activeVariant = v;
}
else {
v.active = false;
}
});
if (!activeVariant) {
// Set the first variant to active if we can't find it.
// If the content item is invariant, then only one item exists in the array.
vm.content.variants[0].active = true;
activeVariant = vm.content.variants[0];
}
insertVariantEditor(0, initVariant(activeVariant));
if (vm.editors.length > 1) {
//now re-sync any other editor content (i.e. if split view is open)
for (var s = 1; s < vm.editors.length; s++) {
//get the variant from the scope model
var variant = _.find(vm.content.variants, function (v) {
return v.language.culture === vm.editors[s].content.language.culture;
});
vm.editors[s].content = initVariant(variant);
}
}
}
/**
* Updates the editors collection for a given index for the specified variant
* @param {any} index
* @param {any} variant
*/
function insertVariantEditor(index, variant) {
var variantCulture = variant.language ? variant.language.culture : "invariant";
//check if the culture at the index is the same, if it's null an editor will be added
var currentCulture = vm.editors.length === 0 || vm.editors.length <= index ? null : vm.editors[index].culture;
if (currentCulture !== variantCulture) {
//Not the current culture which means we need to modify the array.
//NOTE: It is not good enough to just replace the `content` object at a given index in the array
// since that would mean that directives are not re-initialized.
vm.editors.splice(index, 1, {
content: variant,
//used for "track-by" ng-repeat
culture: variantCulture
});
}
else {
//replace the editor for the same culture
vm.editors[index].content = variant;
}
}
function initVariant(variant) {
//The model that is assigned to the editor contains the current content variant along
//with a copy of the contentApps. This is required because each editor renders it's own
//header and content apps section and the content apps contains the view for editing content itself
//and we need to assign a view model to the subView so that it is scoped to the current
//editor so that split views work.
//copy the apps from the main model if not assigned yet to the variant
if (!variant.apps) {
variant.apps = angular.copy(vm.content.apps);
}
//if this is a variant has a culture/language than we need to assign the language drop down info
if (variant.language) {
//if the variant list that defines the header drop down isn't assigned to the variant then assign it now
if (!variant.variants) {
variant.variants = _.map(vm.content.variants,
function (v) {
return _.pick(v, "active", "language", "state");
});
}
else {
//merge the scope variants on top of the header variants collection (handy when needing to refresh)
angular.extend(variant.variants,
_.map(vm.content.variants,
function (v) {
return _.pick(v, "active", "language", "state");
}));
}
//ensure the current culture is set as the active one
for (var i = 0; i < variant.variants.length; i++) {
if (variant.variants[i].language.culture === variant.language.culture) {
variant.variants[i].active = true;
}
else {
variant.variants[i].active = false;
}
}
}
//then assign the variant to a view model to the content app
var contentApp = _.find(variant.apps, function (a) {
return a.alias === "content";
});
contentApp.viewModel = variant;
return variant;
}
/**
* Adds a new editor to the editors array to show content in a split view
* @param {any} selectedVariant
*/
function openSplitView(selectedVariant) {
var selectedCulture = selectedVariant.language.culture;
//only the content app can be selected since no other apps are shown, and because we copy all of these apps
//to the "editors" we need to update this across all editors
for (var e = 0; e < vm.editors.length; e++) {
var editor = vm.editors[e];
for (var i = 0; i < editor.content.apps.length; i++) {
var app = editor.content.apps[i];
if (app.alias === "content") {
app.active = true;
}
else {
app.active = false;
}
}
}
//Find the whole variant model based on the culture that was chosen
var variant = _.find(vm.content.variants, function (v) {
return v.language.culture === selectedCulture;
});
insertVariantEditor(vm.editors.length, initVariant(variant));
//TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
editor.collapsed = true;
editor.loading = true;
$timeout(function () {
editor.collapsed = false;
editor.loading = false;
splitViewChanged();
}, 100);
}
/** Closes the split view */
function closeSplitView(editorIndex) {
//TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
var editor = vm.editors[editorIndex];
editor.loading = true;
editor.collapsed = true;
$timeout(function () {
vm.editors.splice(editorIndex, 1);
splitViewChanged();
}, 400);
}
/**
* Changes the currently selected variant
* @param {any} variant This is the model of the variant/language drop down item in the editor header
* @param {any} editorIndex The index of the editor being changed
*/
function selectVariant(variant, editorIndex) {
var variantCulture = variant.language ? variant.language.culture : "invariant";
//if the editor index is zero, then update the query string to track the lang selection, otherwise if it's part
//of a 2nd split view editor then update the model directly.
if (editorIndex === 0) {
//If we've made it this far, then update the query string.
//The editor will respond to this query string changing.
$location.search("cculture", variant.language.culture);
}
else {
//Update the 'active' variant for this editor
var editor = vm.editors[editorIndex];
//set all variant drop down items as inactive for this editor and then set the selected one as active
for (var i = 0; i < editor.content.variants.length; i++) {
editor.content.variants[i].active = false;
}
variant.active = true;
//get the variant content model and initialize the editor with that
var contentVariant = _.find(vm.content.variants,
function (v) {
return v.language.culture === variant.language.culture;
});
editor.content = initVariant(contentVariant);
//update the editors collection
insertVariantEditor(editorIndex, contentVariant);
}
}
}
angular.module('umbraco.directives').component('umbVariantContentEditors', umbVariantContentEditors);
})();

View File

@@ -209,6 +209,13 @@ Use this directive to construct a header inside the main editor window.
function link(scope, el, attr, ctrl) {
if (!scope.serverValidationNameField) {
scope.serverValidationNameField = "Name";
}
if (!scope.serverValidationAliasField) {
scope.serverValidationAliasField = "Alias";
}
scope.vm = {};
scope.vm.dropdownOpen = false;
scope.vm.currentVariant = "";
@@ -324,7 +331,9 @@ Use this directive to construct a header inside the main editor window.
splitViewOpen: "=?",
onOpenInSplitView: "&?",
onCloseSplitView: "&?",
onSelectVariant: "&?"
onSelectVariant: "&?",
serverValidationNameField: "@?",
serverValidationAliasField: "@?"
},
link: link
};

View File

@@ -22,8 +22,10 @@
// get document type details
scope.mediaType = scope.node.contentType;
// get node url
scope.nodeUrl = scope.node.mediaLink;
// set the media link initially
setMediaLink();
// make sure dates are formatted to the user's locale
formatDatesToLocal();
}
@@ -36,6 +38,10 @@
});
}
function setMediaLink(){
scope.nodeUrl = scope.node.mediaLink;
}
scope.openMediaType = function (mediaType) {
var editor = {
id: mediaType.id,
@@ -48,11 +54,16 @@
};
editorService.mediaTypeEditor(editor);
};
// watch for content updates - reload content when node is saved, published etc.
scope.$watch('node.updateDate', function(newValue, oldValue){
if(!newValue) { return; }
if(newValue === oldValue) { return; }
// Update the media link
setMediaLink();
// Update the create and update dates
formatDatesToLocal();
});
@@ -64,7 +75,6 @@
});
onInit();
}
var directive = {

View File

@@ -412,7 +412,7 @@ Opens an overlay to show a custom YSOD. </br>
(function() {
'use strict';
function OverlayDirective($timeout, formHelper, overlayHelper, localizationService, $q) {
function OverlayDirective($timeout, formHelper, overlayHelper, localizationService, $q, $templateCache, $http, $compile) {
function link(scope, el, attr, ctrl) {
@@ -424,7 +424,8 @@ Opens an overlay to show a custom YSOD. </br>
var numberOfOverlays = 0;
var isRegistered = false;
var modelCopy = {};
var modelCopy = {};
var unsubscribe = [];
function activate() {
@@ -459,6 +460,21 @@ Opens an overlay to show a custom YSOD. </br>
scope.view = "views/common/overlays/" + viewAlias + "/" + viewAlias + ".html";
}
//if a custom parent scope is defined then we need to manually compile the view
if (scope.parentScope) {
var element = el.find(".scoped-view");
$http.get(scope.view, { cache: $templateCache })
.then(function (response) {
var templateScope = scope.parentScope.$new();
unsubscribe.push(function() {
templateScope.$destroy();
});
templateScope.model = scope.model;
element.html(response.data);
element.show();
$compile(element.contents())(templateScope);
});
}
}
}
@@ -553,7 +569,7 @@ Opens an overlay to show a custom YSOD. </br>
var newObject = {};
for (var key in object) {
if (key !== "event") {
if (key !== "event" && key !== "parentScope") {
newObject[key] = angular.copy(object[key]);
}
}
@@ -652,14 +668,14 @@ Opens an overlay to show a custom YSOD. </br>
$q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then(
function() {
formHelper.resetForm({ scope: scope });
}, angular.noop);
});
} else {
unregisterOverlay();
//wrap in a when since we don't know if this is a promise or not
$q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then(
function() {
formHelper.resetForm({ scope: scope });
}, angular.noop);
});
}
}
@@ -684,8 +700,11 @@ Opens an overlay to show a custom YSOD. </br>
};
scope.$on('$destroy', function(){
unregisterOverlay();
unsubscribe.push(unregisterOverlay);
scope.$on('$destroy', function () {
for (var i = 0; i < unsubscribe.length; i++) {
unsubscribe[i]();
}
});
activate();
@@ -701,7 +720,8 @@ Opens an overlay to show a custom YSOD. </br>
ngShow: "=",
model: "=",
view: "=",
position: "@"
position: "@",
parentScope: "=?"
},
link: link
};

View File

@@ -58,9 +58,13 @@
entityResource.getPagedChildren(miniListView.node.id, scope.entityType, miniListView.pagination)
.then(function (data) {
// update children
miniListView.children = data.items;
_.each(miniListView.children, function(c) {
// child allowed by default
c.allowed = true;
// convert legacy icon for node
if(c.icon) {
c.icon = iconHelper.convertFromLegacyIcon(c.icon);
@@ -72,6 +76,17 @@
c.published = c.metaData.IsPublished;
}
}
// filter items if there is a filter and it's not advanced
// ** ignores advanced filter at the moment
if (scope.entityTypeFilter && !scope.entityTypeFilter.filterAdvanced) {
var a = scope.entityTypeFilter.filter.toLowerCase().replace(/\s/g, '').split(',');
var found = a.indexOf(c.metaData.ContentTypeAlias.toLowerCase()) >= 0;
if (!scope.entityTypeFilter.filterExclude && !found || scope.entityTypeFilter.filterExclude && found) {
c.allowed = false;
}
}
});
// update pagination
miniListView.pagination.totalItems = data.totalItems;
@@ -87,7 +102,7 @@
};
scope.selectNode = function(node) {
if(scope.onSelect) {
if (scope.onSelect && node.allowed) {
scope.onSelect({'node': node});
}
};
@@ -184,7 +199,8 @@
entityType: "@",
startNodeId: "=",
onSelect: "&",
onClose: "&"
onClose: "&",
entityTypeFilter: "="
},
link: link
};

View File

@@ -50,6 +50,11 @@ Use this directive make an element sticky and follow the page when scrolling.
function activate() {
if (bar.parents(".umb-property").length > 1) {
bar.addClass("nested");
return;
}
if (attr.scrollableContainer) {
scrollableContainer = $(attr.scrollableContainer);
} else {

View File

@@ -0,0 +1,10 @@
angular.module("umbraco.directives")
.directive('umbIsolateForm', function () {
return {
restrict: 'A',
require: ['form', '^form'],
link: function (scope, element, attrs, forms) {
forms[1].$removeControl(forms[0]);
}
}
});

View File

@@ -3,16 +3,20 @@
function showValidationOnSubmit(serverValidationManager) {
return {
require: "ngMessages",
require: ["ngMessages", "^^?valFormManager"],
restrict: "A",
scope: {
form: "=?"
},
link: function (scope, element, attr, ctrl) {
//We can either get the form submitted status by the parent directive valFormManager (if we add a property to it)
//or we can just check upwards in the DOM for the css class (easier for now).
var formMgr = ctrl.length > 1 ? ctrl[1] : null;
//We can either get the form submitted status by the parent directive valFormManager
//or we can check upwards in the DOM for the css class... lets try both :)
//The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot
//reset the status.
var submitted = element.closest(".show-validation").length > 0;
var submitted = element.closest(".show-validation").length > 0 || (formMgr && formMgr.showValidation);
if (!submitted) {
element.hide();
}

View File

@@ -19,7 +19,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
var SAVED_EVENT_NAME = "formSubmitted";
return {
require: "form",
require: ["form", "^^?valFormManager"],
restrict: "A",
controller: function($scope) {
//This exposes an API for direct use with this directive
@@ -35,6 +35,8 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
}));
};
this.showValidation = $scope.showValidation === true;
//Ensure to remove the event handlers when this instance is destroyted
$scope.$on('$destroy', function () {
for (var u in unsubscribe) {
@@ -42,7 +44,10 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
}
});
},
link: function (scope, element, attr, formCtrl) {
link: function (scope, element, attr, ctrls) {
var formCtrl = ctrls[0];
var parentFormMgr = ctrls.length > 0 ? ctrls[1] : null;
var labels = {};
@@ -96,8 +101,9 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
var isSavingNewItem = false;
//we should show validation if there are any msgs in the server validation collection
if (serverValidationManager.items.length > 0) {
if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.showValidation)) {
element.addClass(SHOW_VALIDATION_CLASS_NAME);
scope.showValidation = true;
}
var unsubscribe = [];
@@ -105,7 +111,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
//listen for the forms saving event
unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function(ev, args) {
element.addClass(SHOW_VALIDATION_CLASS_NAME);
scope.showValidation = true;
//set the flag so we can check to see if we should display the error.
isSavingNewItem = $routeParams.create;
}));
@@ -114,7 +120,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function(ev, args) {
//remove validation class
element.removeClass(SHOW_VALIDATION_CLASS_NAME);
scope.showValidation = false;
//clear form state as at this point we retrieve new data from the server
//and all validation will have cleared at this point
formCtrl.$setPristine();

View File

@@ -26,12 +26,14 @@
function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
/** internal method process the saving of data and post processing the result */
function saveContentItem(content, action, files, restApiUrl) {
function saveContentItem(content, action, files, restApiUrl, showNotifications) {
return umbRequestHelper.postSaveContent({
restApiUrl: restApiUrl,
content: content,
action: action,
files: files,
showNotifications: showNotifications,
dataFormatter: function (c, a) {
return umbDataFormatter.formatContentPostData(c, a);
}
@@ -632,22 +634,23 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
*
* @param {Object} content The content item object with changes applied
* @param {Bool} isNew set to true to create a new item or to update an existing
* @param {Array} files collection of files for the document
* @param {Array} files collection of files for the document
* @param {Bool} showNotifications an option to disable/show notifications (default is true)
* @returns {Promise} resourcePromise object containing the saved content item.
*
*/
save: function (content, isNew, files) {
save: function (content, isNew, files, showNotifications) {
var endpoint = umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostSave");
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint, showNotifications);
},
saveBlueprint: function (content, isNew, files) {
saveBlueprint: function (content, isNew, files, showNotifications) {
var endpoint = umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostSaveBlueprint");
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint, showNotifications);
},
/**
@@ -674,15 +677,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
*
* @param {Object} content The content item object with changes applied
* @param {Bool} isNew set to true to create a new item or to update an existing
* @param {Array} files collection of files for the document
* @param {Array} files collection of files for the document
* @param {Bool} showNotifications an option to disable/show notifications (default is true)
* @returns {Promise} resourcePromise object containing the saved content item.
*
*/
publish: function (content, isNew, files) {
publish: function (content, isNew, files, showNotifications) {
var endpoint = umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostSave");
return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint);
return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint, showNotifications);
},

View File

@@ -298,10 +298,10 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
},
createCollection: function (parentId, collectionName, collectionItemName, collectionIcon, collectionItemIcon) {
createCollection: function (parentId, collectionName, collectionCreateTemplate, collectionItemName, collectionItemCreateTemplate, collectionIcon, collectionItemIcon) {
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateCollection", { parentId: parentId, collectionName: collectionName, collectionItemName: collectionItemName, collectionIcon: collectionIcon, collectionItemIcon: collectionItemIcon })),
$http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateCollection", { parentId: parentId, collectionName: collectionName, collectionCreateTemplate: collectionCreateTemplate, collectionItemName: collectionItemName, collectionItemCreateTemplate: collectionItemCreateTemplate, collectionIcon: collectionIcon, collectionItemIcon: collectionItemIcon})),
'Failed to create collection under ' + parentId);
},

View File

@@ -66,7 +66,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
args.scope.busy = true;
return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles())
return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications)
.then(function (data) {
formHelper.resetForm({ scope: args.scope });
@@ -262,7 +262,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
// if save button is alread the default don't change it just update the label
if (buttons.defaultButton && buttons.defaultButton.letter === "A") {
buttons.defaultButton.labelKey = "buttons_saveAndSchedule";
return;
return buttons;
}
if (buttons.defaultButton && buttons.subButtons && buttons.subButtons.length > 0) {
@@ -439,8 +439,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
var shouldIgnore = function (propName) {
return _.some([
"variants",
"notifications",
"ModelState",
"tabs",
"properties",
"apps",
@@ -599,8 +598,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//add model state errors to notifications
if (args.showNotifications) {
for (var e in modelState) {
notificationsService.error("Validation", modelState[e][0]);
for (var e in args.err.data.ModelState) {
notificationsService.error("Validation", args.err.data.ModelState[e][0]);
}
}

View File

@@ -761,13 +761,13 @@ function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macro
* @param {string} input the string to parse
*/
getAnchorNames: function (input) {
var anchors = [];
if (!input) {
return anchors;
}
var anchors = [];
if (!input) {
return anchors;
}
var anchorPattern = /<a id=\\"(.*?)\\">/gi;
var matches = input.match(anchorPattern);
var matches = input.match(anchorPattern);
if (matches) {
@@ -776,7 +776,9 @@ function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macro
});
}
return anchors;
return anchors.filter(function(val, i, self) {
return self.indexOf(val) === i;
});
},
insertLinkInEditor: function (editor, target, anchorElm) {

View File

@@ -136,8 +136,8 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
//create the callbacs based on whats been passed in.
var callbacks = {
success: ((!opts || !opts.success) ? defaultSuccess : opts.success),
error: ((!opts || !opts.error) ? defaultError : opts.error)
success: (!opts || !opts.success) ? defaultSuccess : opts.success,
error: (!opts || !opts.error ? defaultError : opts.error)
};
return httpPromise.then(function (response) {
@@ -156,7 +156,7 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
//this is a JS/angular error that we should deal with
return $q.reject({
errorMsg: response.message
})
});
}
//invoke the callback
@@ -188,12 +188,22 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
errorMsg: result.errorMsg,
data: result.data,
status: result.status
})
});
});
},
/** Used for saving content/media/members specifically */
/**
* @ngdoc method
* @name umbraco.resources.contentResource#postSaveContent
* @methodOf umbraco.resources.contentResource
*
* @description
* Used for saving content/media/members specifically
*
* @param {Object} args arguments object
* @returns {Promise} http promise object.
*/
postSaveContent: function (args) {
if (!args.restApiUrl) {
@@ -211,6 +221,9 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
if (!args.dataFormatter) {
throw "args.dataFormatter is a required argument";
}
if (args.showNotifications === null || args.showNotifications === undefined) {
args.showNotifications = true;
}
//save the active tab id so we can set it when the data is returned.
var activeTab = _.find(args.content.tabs, function (item) {
@@ -246,7 +259,9 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
response.data.tabs[activeTabIndex].active = true;
}
formHelper.showNotifications(response.data);
if (args.showNotifications) {
formHelper.showNotifications(response.data);
}
//TODO: Do we need to pass the result through umbDataFormatter.formatContentGetData? Right now things work so not sure but we should check
@@ -278,7 +293,7 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
}
}
else {
else if (args.showNotifications) {
formHelper.showNotifications(response.data);
}

View File

@@ -8,7 +8,8 @@
{ "value": 0, "name": "Active", "key": "Active", "color": "success" },
{ "value": 1, "name": "Disabled", "key": "Disabled", "color": "danger" },
{ "value": 2, "name": "Locked out", "key": "LockedOut", "color": "danger" },
{ "value": 3, "name": "Invited", "key": "Invited", "color": "warning" }
{ "value": 3, "name": "Invited", "key": "Invited", "color": "warning" },
{ "value": 4, "name": "Inactive", "key": "Inactive", "color": "warning" }
];
angular.forEach(userStates, function (userState) {

View File

@@ -13,23 +13,31 @@
list-style: none;
align-items: center;
margin: 0;
margin-right: 10px;
margin-right: -10px;
}
.umb-app-header__action a {
color: @white;
opacity: 0.6;
font-size: 22px;
padding-left: 10px;
padding-right: 10px;
text-decoration: none;
display: flex;
align-items: center;
height: @appHeaderHeight;
}
.umb-app-header__action a:hover,
.umb-app-header__action a:focus {
opacity: 1;
outline: none;
}
.umb-app-header__action-icon {
opacity: 0.6;
color: @white;
font-size: 22px;
}
.umb-app-header__action a:hover .umb-app-header__action-icon,
.umb-app-header__action a:focus .umb-app-header__action-icon {
opacity: 1;
}

View File

@@ -1,13 +1,13 @@
.umb-tour__loader {
background: @white;
z-index: 10000;
z-index: @zindexTourModal;
position: fixed;
height: 5px;
}
.umb-tour__pulse {
position: fixed;
z-index: 10000;
z-index: @zindexTourModal;
display: none;
background: transparent;
box-shadow: 0 0 0 @green inset;
@@ -31,7 +31,7 @@
position: fixed;
background: @white;
border-radius: @baseBorderRadius;
z-index: 10000;
z-index: @zindexTourModal;
width: 320px;
max-width: 100%;
box-sizing: border-box;
@@ -100,3 +100,8 @@
font-size: 14px;
line-height: 1.6em;
}
// we need to make sure the tour is on top of everything else
.umb-tour-is-visible .umb-backdrop {
z-index: @zindexTourBackdrop;
}

View File

@@ -7,6 +7,12 @@
margin-top: -20px;
position: relative;
top: 0;
&.nested {
margin-top: 0;
margin-bottom: 0;
background: @gray-10;
}
}
.umb-editor-sub-header.-umb-sticky-bar {

View File

@@ -27,12 +27,18 @@
}
.umb-iconpicker-item a:hover,
.umb-iconpicker-item a:focus,
.umb-iconpicker-item.-selected {
.umb-iconpicker-item a:focus {
background: @gray-10;
outline: none;
}
.umb-iconpicker-item.-selected {
background: @gray-10;
border: solid 1px @turquoise-d1;
border-radius: @baseBorderRadius;
box-sizing: border-box;
}
.umb-iconpicker-item a:active {
background: @gray-10;
}
@@ -54,11 +60,6 @@
border-radius: 3px;
}
// Hide Circle when not active
i.small{
display: none;
}
// Circle behind the checkmark
i.small.active{
font-size: 14px;

View File

@@ -212,3 +212,14 @@
position: relative;
transform: translate(-50%, -25%);
}
// this resolves the layout issue introduced in nested content in 7.12 with the addition of the input for link anchors
// 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%;
}
}

View File

@@ -148,6 +148,10 @@
/* TEMP */
.umb-minilistview {
.umb-table-row.not-allowed { opacity: 0.6; cursor: not-allowed; }
}
.umb-listview .table-striped tbody td {
position: relative
}

View File

@@ -5,7 +5,7 @@
width: 100%;
height: 100%;
position: absolute;
z-index: 10000;
z-index: 65537;
top: 0;
left: 0;
margin: 0 !important;

View File

@@ -331,6 +331,10 @@
@zindexUmbOverlay: 7500;
@zindexOverlayBackdrop: 2000;
// these are used for the tour which should be on top of everything else
@zindexTourBackdrop: 9999;
@zindexTourModal: 10000;
// Sticky bar has a z-index of "500", which is set from javascript in directive
// so set z-index of cropper should be lower to be behind sticky bar.
@zindexCropperOverlay: 499;

View File

@@ -4,7 +4,7 @@
<umb-load-indicator ng-show="page.loading"></umb-load-indicator>
<div class="umb-dashboard" ng-if="!page.loading">
<div class="umb-dashboard" ng-if="!page.loading" data-element='dashboard'>
<div class="umb-dashboard__header" ng-show="dashboard.tabs.length > 1">
<umb-tabs-nav

View File

@@ -186,7 +186,7 @@
<form method="POST" name="requestPasswordResetForm" ng-submit="requestPasswordResetSubmit(email)">
<div class="control-group" ng-class="{error: requestPasswordResetForm.email.$invalid}">
<label><localize key="general_email">Email</localize></label>
<input type="email" ng-model="email" name="email" class="-full-width-input" localize="placeholder" placeholder="@placeholders_email" />
<input type="email" val-email ng-model="email" name="email" class="-full-width-input" localize="placeholder" placeholder="@placeholders_email" />
</div>
<div class="control-group" ng-show="requestPasswordResetForm.$invalid">

View File

@@ -1,5 +1,5 @@
<div ng-controller="Umbraco.Editors.DataTypePickerController as vm">
<umb-editor-view>
<umb-editor-view data-element="editor-data-type-picker">
<form novalidate name="DataTypePickerForm" val-form-manager>

View File

@@ -1,5 +1,5 @@
<div ng-controller="Umbraco.Editors.DataTypeSettingsController as vm">
<umb-editor-view>
<umb-editor-view data-element="editor-data-type-settings">
<form novalidate name="dataTypeSettingsForm" val-form-manager>
@@ -55,6 +55,7 @@
action="vm.close()">
</umb-button>
<umb-button
alias="submit"
type="button"
button-style="success"
label-key="general_submit"

View File

@@ -4,7 +4,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
var vm = this;
var dialogOptions = $scope.model;
var anchorPattern = /<a id=\\"(.*?)\\">/gi;
var searchText = "Search...";
vm.submit = submit;
@@ -60,8 +60,14 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
});
} else if ($scope.model.target.url.length) {
// a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
$scope.model.target.url = $scope.model.target.url.substring(0, $scope.model.target.url.search(/(#|\?)/));
}
// only do the substring if there's a # or a ?
var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/);
if (indexOfAnchor > -1) {
// populate the anchor
$scope.model.target.anchor = $scope.model.target.url.substring(indexOfAnchor);
// then rewrite the model and populate the link
$scope.model.target.url = $scope.model.target.url.substring(0, indexOfAnchor);
} }
} else if (dialogOptions.anchors) {
$scope.anchorValues = dialogOptions.anchors;
}

Some files were not shown because too many files have changed in this diff Show More