Merge branch 'temp8' into temp8-ui-cleanup
This commit is contained in:
@@ -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")]
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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)]
|
||||
@@ -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
|
||||
@@ -58,6 +58,7 @@ namespace Umbraco.Core.Composing
|
||||
// resets *everything* that is 'current'
|
||||
internal static void Reset()
|
||||
{
|
||||
_container?.Dispose();
|
||||
_container = null;
|
||||
|
||||
_shortStringHelper = null;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
bool DisableAlternativeTemplates { get; }
|
||||
|
||||
bool ValidateAlternativeTemplates { get; }
|
||||
|
||||
bool DisableFindContentByIdPath { get; }
|
||||
|
||||
bool DisableRedirectUrlTracking { get; }
|
||||
@@ -16,5 +18,4 @@
|
||||
|
||||
string UmbracoApplicationUrl { get; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
15
src/Umbraco.Core/Logging/LogLevel.cs
Normal file
15
src/Umbraco.Core/Logging/LogLevel.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
35
src/Umbraco.Core/Logging/MessageTemplates.cs
Normal file
35
src/Umbraco.Core/Logging/MessageTemplates.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -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" />
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
Active = 0,
|
||||
Disabled = 1,
|
||||
LockedOut = 2,
|
||||
Invited = 3
|
||||
Invited = 3,
|
||||
Inactive = 4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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('-'); }
|
||||
|
||||
@@ -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 ");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Macros;
|
||||
using Umbraco.Web.Macros;
|
||||
|
||||
namespace Umbraco.Tests.Macros
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" };
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>()));
|
||||
}
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
src/common/services/util.service.js
|
||||
File diff suppressed because one or more lines are too long
4374
src/Umbraco.Web.UI.Client/package-lock.json
generated
4374
src/Umbraco.Web.UI.Client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
8
src/Umbraco.Web.UI.Client/src/assets/fonts/web.config
Normal file
8
src/Umbraco.Web.UI.Client/src/assets/fonts/web.config
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
},
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
z-index: 65537;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 !important;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user