Port v7@2aa0dfb2c5 - WIP

This commit is contained in:
Stephan
2018-03-22 17:41:13 +01:00
parent 62c5f0f1ef
commit a2a4edb3be
37 changed files with 965 additions and 220 deletions

View File

@@ -3,19 +3,21 @@ 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;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Auditing
{
public sealed class AuditEventHandler : ApplicationEventHandler
public sealed class AuditEventsComponent : UmbracoComponentBase, IUmbracoCoreComponent
{
private IAuditService _auditServiceInstance;
private IUserService _userServiceInstance;
private IEntityService _entityServiceInstance;
private IAuditService _auditService;
private IUserService _userService;
private IEntityService _entityService;
private IUser CurrentPerformingUser
{
@@ -24,13 +26,13 @@ namespace Umbraco.Core.Auditing
var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity();
return identity == null
? new User { Id = 0, Name = "SYSTEM", Email = "" }
: _userServiceInstance.GetUserById(Convert.ToInt32(identity.Id));
: _userService.GetUserById(Convert.ToInt32(identity.Id));
}
}
private IUser GetPerformingUser(int userId)
{
var found = userId >= 0 ? _userServiceInstance.GetUserById(userId) : null;
var found = userId >= 0 ? _userService.GetUserById(userId) : null;
return found ?? new User {Id = 0, Name = "SYSTEM", Email = ""};
}
@@ -45,11 +47,11 @@ namespace Umbraco.Core.Auditing
}
}
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
public void Initialize(IAuditService auditService, IUserService userService, IEntityService entityService)
{
_auditServiceInstance = applicationContext.Services.AuditService;
_userServiceInstance = applicationContext.Services.UserService;
_entityServiceInstance = applicationContext.Services.EntityService;
_auditService = auditService;
_userService = userService;
_entityService = entityService;
//BackOfficeUserManager.AccountLocked += ;
//BackOfficeUserManager.AccountUnlocked += ;
@@ -63,7 +65,7 @@ namespace Umbraco.Core.Auditing
BackOfficeUserManager.PasswordReset += OnPasswordReset;
//BackOfficeUserManager.ResetAccessFailedCount += ;
UserService.SavedUserGroup2 += OnSavedUserGroupWithUsers;
UserService.SavedUserGroup += OnSavedUserGroupWithUsers;
UserService.SavedUser += OnSavedUser;
UserService.DeletedUser += OnDeletedUser;
@@ -94,7 +96,7 @@ namespace Umbraco.Core.Auditing
foreach (var id in args.MemberIds)
{
members.TryGetValue(id, out var member);
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}",
"umbraco/member/roles/removed", $"roles modified, removed {roles}");
@@ -109,7 +111,7 @@ namespace Umbraco.Core.Auditing
foreach (var id in args.MemberIds)
{
members.TryGetValue(id, out var member);
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}",
"umbraco/member/roles/assigned", $"roles modified, assigned {roles}");
@@ -121,7 +123,7 @@ namespace Umbraco.Core.Auditing
var performingUser = CurrentPerformingUser;
var member = exportedMemberEventArgs.Member;
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
"umbraco/member/exported", "exported member data");
@@ -134,7 +136,7 @@ namespace Umbraco.Core.Auditing
{
var group = groupWithUser.UserGroup;
var dp = string.Join(", ", ((UserGroup)group).GetPreviouslyDirtyProperties());
var dp = string.Join(", ", ((UserGroup)group).GetWereDirtyProperties());
var sections = ((UserGroup)group).WasPropertyDirty("AllowedSections")
? string.Join(", ", group.AllowedSections)
: null;
@@ -153,7 +155,7 @@ namespace Umbraco.Core.Auditing
sb.Append($"default perms: {perms}");
}
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})",
"umbraco/user-group/save", $"{sb}");
@@ -162,7 +164,7 @@ namespace Umbraco.Core.Auditing
foreach (var user in groupWithUser.RemovedUsers)
{
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
user.Id, $"User \"{user.Name}\" {FormatEmail(user)}",
"umbraco/user-group/save", $"Removed user \"{user.Name}\" {FormatEmail(user)} from group {group.Id} \"{group.Name}\" ({group.Alias})");
@@ -170,7 +172,7 @@ namespace Umbraco.Core.Auditing
foreach (var user in groupWithUser.AddedUsers)
{
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
user.Id, $"User \"{user.Name}\" {FormatEmail(user)}",
"umbraco/user-group/save", $"Added user \"{user.Name}\" {FormatEmail(user)} to group {group.Id} \"{group.Name}\" ({group.Alias})");
@@ -186,9 +188,9 @@ namespace Umbraco.Core.Auditing
{
var group = sender.GetUserGroupById(perm.UserGroupId);
var assigned = string.Join(", ", perm.AssignedPermissions);
var entity = _entityServiceInstance.Get(perm.EntityId);
var entity = _entityService.Get(perm.EntityId);
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})",
"umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity.Name}\"");
@@ -201,9 +203,9 @@ namespace Umbraco.Core.Auditing
var members = saveEventArgs.SavedEntities;
foreach (var member in members)
{
var dp = string.Join(", ", ((Member) member).GetPreviouslyDirtyProperties());
var dp = string.Join(", ", ((Member) member).GetWereDirtyProperties());
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
"umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}");
@@ -216,7 +218,7 @@ namespace Umbraco.Core.Auditing
var members = deleteEventArgs.DeletedEntities;
foreach (var member in members)
{
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
-1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}",
"umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}");
@@ -233,9 +235,9 @@ namespace Umbraco.Core.Auditing
? string.Join(", ", affectedUser.Groups.Select(x => x.Alias))
: null;
var dp = string.Join(", ", ((User)affectedUser).GetPreviouslyDirtyProperties());
var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties());
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}",
"umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}");
@@ -247,7 +249,7 @@ namespace Umbraco.Core.Auditing
var performingUser = CurrentPerformingUser;
var affectedUsers = deleteEventArgs.DeletedEntities;
foreach (var affectedUser in affectedUsers)
_auditServiceInstance.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
_auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp,
DateTime.UtcNow,
affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}",
"umbraco/user/delete", "delete user");
@@ -313,7 +315,7 @@ namespace Umbraco.Core.Auditing
private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null)
{
var performingUser = _userServiceInstance.GetUserById(performingId);
var performingUser = _userService.GetUserById(performingId);
var performingDetails = performingUser == null
? $"User UNKNOWN:{performingId}"
@@ -335,13 +337,13 @@ namespace Umbraco.Core.Auditing
{
if (affectedDetails == null)
{
var affectedUser = _userServiceInstance.GetUserById(affectedId);
var affectedUser = _userService.GetUserById(affectedId);
affectedDetails = affectedUser == null
? $"User UNKNOWN:{affectedId}"
: $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}";
}
_auditServiceInstance.Write(performingId, performingDetails,
_auditService.Write(performingId, performingDetails,
ipAddress,
DateTime.UtcNow,
affectedId, affectedDetails,

View File

@@ -136,5 +136,11 @@ namespace Umbraco.Core.Collections
dc.ResetDirtyProperties(rememberDirty);
}
}
/// <remarks>Always return an empty enumerable, the list has no properties that can be dirty.</remarks>
public IEnumerable<string> GetWereDirtyProperties()
{
return Enumerable.Empty<string>();
}
}
}

View File

@@ -483,7 +483,7 @@ namespace Umbraco.Core.Configuration
{
get
{
var setting = ConfigurationManager.AppSettings.ContainsKey("umbracoLocalTempStorage");
var setting = ConfigurationManager.AppSettings["umbracoLocalTempStorage"];
if (!string.IsNullOrWhiteSpace(setting))
return Enum<LocalTempStorage>.Parse(setting);

View File

@@ -126,8 +126,6 @@
public const string Languages = "languages";
public const string Macros = "macros";
/// <summary>
/// alias for the user types tree.
/// </summary>

View File

@@ -215,6 +215,11 @@ namespace Umbraco.Core.Models.Entities
throw new WontImplementException();
}
public IEnumerable<string> GetWereDirtyProperties()
{
throw new WontImplementException();
}
#endregion
}
}

View File

@@ -390,6 +390,10 @@ namespace Umbraco.Core.Models.Identity
_beingDirty.ResetDirtyProperties(rememberDirty);
}
/// <inheritdoc />
public IEnumerable<string> GetWereDirtyProperties()
=> _beingDirty.GetWereDirtyProperties();
/// <summary>
/// Disables change tracking.
/// </summary>

View File

@@ -0,0 +1,11 @@
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Represents the configuration for the color picker value editor.
/// </summary>
public class ColorPickerConfiguration : ValueListConfiguration
{
[ConfigurationField("useLabel", "Include labels?", "boolean", Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string.")]
public bool UseLabel { get; set; }
}
}

View File

@@ -5,6 +5,7 @@ using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -179,6 +180,9 @@ namespace Umbraco.Core.PropertyEditors
/// <returns></returns>
internal Attempt<object> TryConvertValueToCrlType(object value)
{
if (value is JValue)
value = value.ToString();
//this is a custom check to avoid any errors, if it's a string and it's empty just make it null
if (value is string s && string.IsNullOrWhiteSpace(s))
value = null;

View File

@@ -1,4 +1,6 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.PropertyEditors.ValueConverters
@@ -10,15 +12,50 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.ColorPicker);
public override Type GetPropertyValueType(PublishedPropertyType propertyType)
=> typeof (string);
=> UseLabel(propertyType) ? typeof(PickedColor) : typeof(string);
public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview)
{
// make sure it's a string
return source?.ToString() ?? string.Empty;
var useLabel = UseLabel(propertyType);
if (source == null) return useLabel ? null : string.Empty;
var ssource = source.ToString();
if (ssource.DetectIsJson())
{
try
{
var jo = JsonConvert.DeserializeObject<JObject>(ssource);
if (useLabel) return new PickedColor(jo["value"].ToString(), jo["label"].ToString());
return jo["value"].ToString();
}
catch { /* not json finally */ }
}
if (useLabel) return new PickedColor(ssource, ssource);
return ssource;
}
private bool UseLabel(PublishedPropertyType propertyType)
{
return ConfigurationEditor.ConfigurationAs<ColorPickerConfiguration>(propertyType.DataType.Configuration).UseLabel;
}
public class PickedColor
{
public PickedColor(string color, string label)
{
Color = color;
Label = label;
}
public string Color { get; }
public string Label { get; }
public override string ToString() => Color;
}
}
}

View File

@@ -51,8 +51,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
var gridConfig = UmbracoConfig.For.GridConfig(
Current.ProfilingLogger.Logger,
Current.ApplicationCache.RuntimeCache,
new DirectoryInfo(HttpContext.Current.Server.MapPath(SystemDirectories.AppPlugins)),
new DirectoryInfo(HttpContext.Current.Server.MapPath(SystemDirectories.Config)),
new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)),
new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config)),
HttpContext.Current.IsDebuggingEnabled);
var sections = GetArray(obj, "sections");

View File

@@ -18,6 +18,8 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" };
public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview)
{
// data is (both in database and xml):
@@ -52,7 +54,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
// fall back on normal behaviour
return values.Any() == false
? sourceString.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)
? sourceString.Split(NewLineDelimiters, StringSplitOptions.None)
: values.ToArray();
}

View File

@@ -1,12 +1,12 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Umbraco.Web.PropertyEditors
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Represents the ValueList editor configuration.
/// </summary>
class ValueListConfiguration
public class ValueListConfiguration
{
[JsonProperty("items")]
public List<ValueListItem> Items { get; set; } = new List<ValueListItem>();

View File

@@ -324,7 +324,7 @@ namespace Umbraco.Core.Security
Guid guidSession;
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
{
ApplicationContext.Current.Services.UserService.ClearLoginSession(guidSession);
Current.Services.UserService.ClearLoginSession(guidSession);
}
}
}

View File

@@ -1,30 +1,16 @@
using System;
using System.Linq;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Services;
namespace Umbraco.Core.Security
{
public class BackOfficeClaimsIdentityFactory<T> : ClaimsIdentityFactory<T, int>
where T: BackOfficeIdentityUser
{
private readonly ApplicationContext _appCtx;
[Obsolete("Use the overload specifying all dependencies instead")]
public BackOfficeClaimsIdentityFactory()
:this(ApplicationContext.Current)
{
}
public BackOfficeClaimsIdentityFactory(ApplicationContext appCtx)
{
if (appCtx == null) throw new ArgumentNullException("appCtx");
_appCtx = appCtx;
SecurityStampClaimType = Constants.Security.SessionIdClaimType;
UserNameClaimType = ClaimTypes.Name;
}
@@ -58,14 +44,5 @@ namespace Umbraco.Core.Security
}
public class BackOfficeClaimsIdentityFactory : BackOfficeClaimsIdentityFactory<BackOfficeIdentityUser>
{
[Obsolete("Use the overload specifying all dependencies instead")]
public BackOfficeClaimsIdentityFactory()
{
}
public BackOfficeClaimsIdentityFactory(ApplicationContext appCtx) : base(appCtx)
{
}
}
{ }
}

View File

@@ -1,52 +1,37 @@
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Globalization;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Semver;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Services;
namespace Umbraco.Core.Security
{
public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider
{
private readonly ApplicationContext _appCtx;
private readonly IUserService _userService;
private readonly IRuntimeState _runtimeState;
[Obsolete("Use the ctor specifying all dependencies")]
[EditorBrowsable(EditorBrowsableState.Never)]
public BackOfficeCookieAuthenticationProvider()
: this(ApplicationContext.Current)
public BackOfficeCookieAuthenticationProvider(IUserService userService, IRuntimeState runtimeState)
{
_userService = userService;
_runtimeState = runtimeState;
}
public BackOfficeCookieAuthenticationProvider(ApplicationContext appCtx)
{
if (appCtx == null) throw new ArgumentNullException("appCtx");
_appCtx = appCtx;
}
private static readonly SemVersion MinUmbracoVersionSupportingLoginSessions = new SemVersion(7, 8);
public override void ResponseSignIn(CookieResponseSignInContext context)
{
var backOfficeIdentity = context.Identity as UmbracoBackOfficeIdentity;
if (backOfficeIdentity != null)
if (context.Identity is UmbracoBackOfficeIdentity backOfficeIdentity)
{
//generate a session id and assign it
//create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one
//NOTE - special check because when we are upgrading to 7.8 we cannot create a session since the db isn't ready and we'll get exceptions
var canAcquireSession = _appCtx.IsUpgrading == false || _appCtx.CurrentVersion() >= MinUmbracoVersionSupportingLoginSessions;
var session = canAcquireSession
? _appCtx.Services.UserService.CreateLoginSession((int)backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress())
var session = _runtimeState.Level == RuntimeLevel.Run
? _userService.CreateLoginSession((int)backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress())
: Guid.NewGuid();
backOfficeIdentity.UserData.SessionId = session.ToString();
@@ -58,15 +43,13 @@ namespace Umbraco.Core.Security
public override void ResponseSignOut(CookieResponseSignOutContext context)
{
//Clear the user's session on sign out
if (context != null && context.OwinContext != null && context.OwinContext.Authentication != null
&& context.OwinContext.Authentication.User != null && context.OwinContext.Authentication.User.Identity != null)
if (context?.OwinContext?.Authentication?.User?.Identity != null)
{
var claimsIdentity = context.OwinContext.Authentication.User.Identity as ClaimsIdentity;
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
Guid guidSession;
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out var guidSession))
{
_appCtx.Services.UserService.ClearLoginSession(guidSession);
_userService.ClearLoginSession(guidSession);
}
}
@@ -117,7 +100,7 @@ namespace Umbraco.Core.Security
/// </remarks>
protected virtual async Task EnsureValidSessionId(CookieValidateIdentityContext context)
{
if (_appCtx.IsConfigured && _appCtx.IsUpgrading == false)
if (_runtimeState.Level == RuntimeLevel.Run)
await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context);
}

View File

@@ -54,6 +54,8 @@ namespace Umbraco.Core.Services
xml.Add(new XAttribute("template", content.Template?.Id.ToString(CultureInfo.InvariantCulture) ?? "0"));
xml.Add(new XAttribute("isPublished", content.Published));
if (withDescendants)
{
var descendants = contentService.GetDescendants(content).ToArray();

View File

@@ -1,15 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Services
{
/// <summary>
/// Represents a service for handling audit.
/// </summary>
public interface IAuditService : IService
{
void Add(AuditType type, string comment, int userId, int objectId);
IEnumerable<AuditItem> GetLogs(int objectId);
IEnumerable<AuditItem> GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null);
IEnumerable<AuditItem> GetLogs(AuditType type, DateTime? sinceDate = null);
IEnumerable<IAuditItem> GetLogs(int objectId);
IEnumerable<IAuditItem> GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null);
IEnumerable<IAuditItem> GetLogs(AuditType type, DateTime? sinceDate = null);
void CleanLogs(int maximumAgeOfLogsInMinutes);
/// <summary>
/// Returns paged items in the audit trail for a given entity
/// </summary>
/// <param name="entityId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// Optional filter to be applied
/// </param>
/// <returns></returns>
IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
Direction orderDirection = Direction.Descending,
AuditType[] auditTypeFilter = null,
IQuery<IAuditItem> customFilter = null);
/// <summary>
/// Returns paged items in the audit trail for a given user
/// </summary>
/// <param name="userId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// Optional filter to be applied
/// </param>
/// <returns></returns>
IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords,
Direction orderDirection = Direction.Descending,
AuditType[] auditTypeFilter = null,
IQuery<IAuditItem> customFilter = null);
/// <summary>
/// Writes an audit entry for an audited event.
/// </summary>
/// <param name="performingUserId">The identifier of the user triggering the audited event.</param>
/// <param name="perfomingDetails">Free-form details about the user triggering the audited event.</param>
/// <param name="performingIp">The IP address or the request triggering the audited event.</param>
/// <param name="eventDateUtc">The date and time of the audited event.</param>
/// <param name="affectedUserId">The identifier of the user affected by the audited event.</param>
/// <param name="affectedDetails">Free-form details about the entity affected by the audited event.</param>
/// <param name="eventType">
/// The type of the audited event - must contain only alphanumeric chars and hyphens with forward slashes separating categories.
/// <example>
/// The eventType will generally be formatted like: {application}/{entity-type}/{category}/{sub-category}
/// Example: umbraco/user/sign-in/failed
/// </example>
/// </param>
/// <param name="eventDetails">Free-form details about the audited event.</param>
IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string affectedDetails, string eventType, string eventDetails);
}
}

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
/// <summary>
/// A service for handling lawful data processing requirements
/// </summary>
/// <remarks>
/// <para>Consent can be given or revoked or changed via the <see cref="RegisterConsent"/> method, which
/// creates a new <see cref="IConsent"/> entity to track the consent. Revoking a consent is performed by
/// registering a revoked consent.</para>
/// <para>A consent can be revoked, by registering a revoked consent, but cannot be deleted.</para>
/// <para>Getter methods return the current state of a consent, i.e. the latest <see cref="IConsent"/>
/// entity that was created.</para>
/// </remarks>
public interface IConsentService : IService
{
/// <summary>
/// Registers consent.
/// </summary>
/// <param name="source">The source, i.e. whoever is consenting.</param>
/// <param name="context"></param>
/// <param name="action"></param>
/// <param name="state">The state of the consent.</param>
/// <param name="comment">Additional free text.</param>
/// <returns>The corresponding consent entity.</returns>
IConsent RegisterConsent(string source, string context, string action, ConsentState state, string comment = null);
/// <summary>
/// Retrieves consents.
/// </summary>
/// <param name="source">The optional source.</param>
/// <param name="context">The optional context.</param>
/// <param name="action">The optional action.</param>
/// <param name="sourceStartsWith">Determines whether <paramref name="source"/> is a start pattern.</param>
/// <param name="contextStartsWith">Determines whether <paramref name="context"/> is a start pattern.</param>
/// <param name="actionStartsWith">Determines whether <paramref name="action"/> is a start pattern.</param>
/// <param name="includeHistory">Determines whether to include the history of consents.</param>
/// <returns>Consents matching the paramters.</returns>
IEnumerable<IConsent> LookupConsent(string source = null, string context = null, string action = null,
bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
bool includeHistory = false);
}
}

View File

@@ -44,6 +44,16 @@ namespace Umbraco.Core.Services
/// </summary>
IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0);
/// <summary>
/// Deletes blueprints for a content type.
/// </summary>
void DeleteBlueprintsOfType(int contentTypeId, int userId = 0);
/// <summary>
/// Deletes blueprints for content types.
/// </summary>
void DeleteBlueprintsOfTypes(IEnumerable<int> contentTypeIds, int userId = 0);
#endregion
#region Get, Count Documents
@@ -326,6 +336,11 @@ namespace Umbraco.Core.Services
/// </summary>
bool Sort(IEnumerable<IContent> items, int userId = 0, bool raiseEvents = true);
/// <summary>
/// Sorts documents.
/// </summary>
bool Sort(IEnumerable<int> ids, int userId = 0, bool raiseEvents = true);
#endregion
#region Publish Document

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
@@ -12,6 +13,34 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IUserService : IMembershipUserService
{
/// <summary>
/// Creates a database entry for starting a new login session for a user
/// </summary>
/// <param name="userId"></param>
/// <param name="requestingIpAddress"></param>
/// <returns></returns>
Guid CreateLoginSession(int userId, string requestingIpAddress);
/// <summary>
/// Validates that a user login session is valid/current and hasn't been closed
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
bool ValidateLoginSession(int userId, Guid sessionId);
/// <summary>
/// Removes the session's validity
/// </summary>
/// <param name="sessionId"></param>
void ClearLoginSession(Guid sessionId);
/// <summary>
/// Removes all valid sessions for the user
/// </summary>
/// <param name="userId"></param>
int ClearLoginSessions(int userId);
/// <summary>
/// This is basically facets of UserStates key = state, value = count
/// </summary>

View File

@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Threading;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Services
@@ -37,8 +39,13 @@ namespace Umbraco.Core.Services
int? val;
using (var scope = _scopeProvider.CreateScope())
{
val = scope.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id AND nodeObjectType=@nodeObjectType",
new { id = key, nodeObjectType = GetNodeObjectTypeGuid(umbracoObjectType) });
var sql = scope.Database.SqlContext.Sql()
.Select<NodeDto>(x => x.NodeId).From<NodeDto>().Where<NodeDto>(x => x.UniqueId == key);
if (umbracoObjectType != UmbracoObjectTypes.Unknown) // if unknow, don't include in query
sql = sql.Where<NodeDto>(x => x.NodeObjectType == GetNodeObjectTypeGuid(umbracoObjectType) || x.NodeObjectType == Constants.ObjectTypes.IdReservation); // fixme TEST the OR here!
val = scope.Database.ExecuteScalar<int?>(sql);
scope.Complete();
}
@@ -89,8 +96,13 @@ namespace Umbraco.Core.Services
Guid? val;
using (var scope = _scopeProvider.CreateScope())
{
val = scope.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id AND nodeObjectType=@nodeObjectType",
new { id, nodeObjectType = GetNodeObjectTypeGuid(umbracoObjectType) });
var sql = scope.Database.SqlContext.Sql()
.Select<NodeDto>(x => x.UniqueId).From<NodeDto>().Where<NodeDto>(x => x.NodeId == id);
if (umbracoObjectType != UmbracoObjectTypes.Unknown) // if unknow, don't include in query
sql = sql.Where<NodeDto>(x => x.NodeObjectType == GetNodeObjectTypeGuid(umbracoObjectType) || x.NodeObjectType == Constants.ObjectTypes.IdReservation); // fixme TEST the OR here!
val = scope.Database.ExecuteScalar<Guid?>(sql);
scope.Complete();
}
@@ -104,7 +116,7 @@ namespace Umbraco.Core.Services
{
_locker.EnterWriteLock();
_id2Key[id] = new TypedId<Guid>(val.Value, umbracoObjectType);
_key2Id[val.Value] = new TypedId<int>();
_key2Id[val.Value] = new TypedId<int>(id, umbracoObjectType);
}
finally
{

View File

@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Scoping;
@@ -10,13 +14,17 @@ namespace Umbraco.Core.Services.Implement
{
public sealed class AuditService : ScopeRepositoryService, IAuditService
{
private readonly Lazy<bool> _isAvailable;
private readonly IAuditRepository _auditRepository;
private readonly IAuditEntryRepository _auditEntryRepository;
public AuditService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
IAuditRepository auditRepository)
IAuditRepository auditRepository, IAuditEntryRepository auditEntryRepository)
: base(provider, logger, eventMessagesFactory)
{
_auditRepository = auditRepository;
_auditEntryRepository = auditEntryRepository;
_isAvailable = new Lazy<bool>(DetermineIsAvailable);
}
public void Add(AuditType type, string comment, int userId, int objectId)
@@ -28,35 +36,35 @@ namespace Umbraco.Core.Services.Implement
}
}
public IEnumerable<AuditItem> GetLogs(int objectId)
public IEnumerable<IAuditItem> GetLogs(int objectId)
{
using (var scope = ScopeProvider.CreateScope())
{
var result = _auditRepository.Get(Query<AuditItem>().Where(x => x.Id == objectId));
var result = _auditRepository.Get(Query<IAuditItem>().Where(x => x.Id == objectId));
scope.Complete();
return result;
}
}
public IEnumerable<AuditItem> GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null)
public IEnumerable<IAuditItem> GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null)
{
using (var scope = ScopeProvider.CreateScope())
{
var result = sinceDate.HasValue == false
? _auditRepository.Get(Query<AuditItem>().Where(x => x.UserId == userId && x.AuditType == type))
: _auditRepository.Get(Query<AuditItem>().Where(x => x.UserId == userId && x.AuditType == type && x.CreateDate >= sinceDate.Value));
? _auditRepository.Get(Query<IAuditItem>().Where(x => x.UserId == userId && x.AuditType == type))
: _auditRepository.Get(Query<IAuditItem>().Where(x => x.UserId == userId && x.AuditType == type && x.CreateDate >= sinceDate.Value));
scope.Complete();
return result;
}
}
public IEnumerable<AuditItem> GetLogs(AuditType type, DateTime? sinceDate = null)
public IEnumerable<IAuditItem> GetLogs(AuditType type, DateTime? sinceDate = null)
{
using (var scope = ScopeProvider.CreateScope())
{
var result = sinceDate.HasValue == false
? _auditRepository.Get(Query<AuditItem>().Where(x => x.AuditType == type))
: _auditRepository.Get(Query<AuditItem>().Where(x => x.AuditType == type && x.CreateDate >= sinceDate.Value));
? _auditRepository.Get(Query<IAuditItem>().Where(x => x.AuditType == type))
: _auditRepository.Get(Query<IAuditItem>().Where(x => x.AuditType == type && x.CreateDate >= sinceDate.Value));
scope.Complete();
return result;
}
@@ -70,5 +78,163 @@ namespace Umbraco.Core.Services.Implement
scope.Complete();
}
}
/// <summary>
/// Returns paged items in the audit trail for a given entity
/// </summary>
/// <param name="entityId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// Optional filter to be applied
/// </param>
/// <returns></returns>
public IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
Direction orderDirection = Direction.Descending,
AuditType[] auditTypeFilter = null,
IQuery<IAuditItem> customFilter = null)
{
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
if (entityId == Constants.System.Root || entityId <= 0)
{
totalRecords = 0;
return Enumerable.Empty<IAuditItem>();
}
using (ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IAuditItem>().Where(x => x.Id == entityId);
return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
}
}
/// <summary>
/// Returns paged items in the audit trail for a given user
/// </summary>
/// <param name="userId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// Optional filter to be applied
/// </param>
/// <returns></returns>
public IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[] auditTypeFilter = null, IQuery<IAuditItem> customFilter = null)
{
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
if (userId < 0)
{
totalRecords = 0;
return Enumerable.Empty<IAuditItem>();
}
using (ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IAuditItem>().Where(x => x.UserId == userId);
return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
}
}
/// <inheritdoc />
public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string affectedDetails, string eventType, string eventDetails)
{
if (performingUserId < 0) throw new ArgumentOutOfRangeException(nameof(performingUserId));
if (string.IsNullOrWhiteSpace(perfomingDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(perfomingDetails));
if (string.IsNullOrWhiteSpace(eventType)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventType));
if (string.IsNullOrWhiteSpace(eventDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventDetails));
//we need to truncate the data else we'll get SQL errors
affectedDetails = affectedDetails?.Substring(0, Math.Min(affectedDetails.Length, AuditEntryDto.DetailsLength));
eventDetails = eventDetails.Substring(0, Math.Min(eventDetails.Length, AuditEntryDto.DetailsLength));
//validate the eventType - must contain a forward slash, no spaces, no special chars
var eventTypeParts = eventType.ToCharArray();
if (eventTypeParts.Contains('/') == false || eventTypeParts.All(c => char.IsLetterOrDigit(c) || c == '/' || c == '-') == false)
throw new ArgumentException(nameof(eventType) + " must contain only alphanumeric characters, hyphens and at least one '/' defining a category");
if (eventType.Length > AuditEntryDto.EventTypeLength)
throw new ArgumentException($"Must be max {AuditEntryDto.EventTypeLength} chars.", nameof(eventType));
if (performingIp != null && performingIp.Length > AuditEntryDto.IpLength)
throw new ArgumentException($"Must be max {AuditEntryDto.EventTypeLength} chars.", nameof(performingIp));
var entry = new AuditEntry
{
PerformingUserId = performingUserId,
PerformingDetails = perfomingDetails,
PerformingIp = performingIp,
EventDateUtc = eventDateUtc,
AffectedUserId = affectedUserId,
AffectedDetails = affectedDetails,
EventType = eventType,
EventDetails = eventDetails
};
if (_isAvailable.Value == false) return entry;
using (var scope = ScopeProvider.CreateScope())
{
_auditEntryRepository.Save(entry);
scope.Complete();
}
return entry;
}
//TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
internal IEnumerable<IAuditEntry> GetAll()
{
if (_isAvailable.Value == false) return Enumerable.Empty<IAuditEntry>();
using (ScopeProvider.CreateScope(autoComplete: true))
{
return _auditEntryRepository.GetMany();
}
}
//TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
internal IEnumerable<IAuditEntry> GetPage(long pageIndex, int pageCount, out long records)
{
if (_isAvailable.Value == false)
{
records = 0;
return Enumerable.Empty<IAuditEntry>();
}
using (ScopeProvider.CreateScope(autoComplete: true))
{
return _auditEntryRepository.GetPage(pageIndex, pageCount, out records);
}
}
/// <summary>
/// Determines whether the repository is available.
/// </summary>
private bool DetermineIsAvailable()
{
using (ScopeProvider.CreateScope(autoComplete: true))
{
return _auditEntryRepository.IsAvailable();
}
}
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Services.Implement
{
/// <summary>
/// Implements <see cref="IContentService"/>.
/// </summary>
internal class ConsentService : ScopeRepositoryService, IConsentService
{
private readonly IConsentRepository _consentRepository;
/// <summary>
/// Initializes a new instance of the <see cref="ContentService"/> class.
/// </summary>
public ConsentService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IConsentRepository consentRepository)
: base(provider, logger, eventMessagesFactory)
{
_consentRepository = consentRepository;
}
/// <inheritdoc />
public IConsent RegisterConsent(string source, string context, string action, ConsentState state, string comment = null)
{
// prevent stupid states
var v = 0;
if ((state & ConsentState.Pending) > 0) v++;
if ((state & ConsentState.Granted) > 0) v++;
if ((state & ConsentState.Revoked) > 0) v++;
if (v != 1)
throw new ArgumentException("Invalid state.", nameof(state));
var consent = new Consent
{
Current = true,
Source = source,
Context = context,
Action = action,
CreateDate = DateTime.Now,
State = state,
Comment = comment
};
using (var scope = ScopeProvider.CreateScope())
{
_consentRepository.ClearCurrent(source, context, action);
_consentRepository.Save(consent);
scope.Complete();
}
return consent;
}
/// <inheritdoc />
public IEnumerable<IConsent> LookupConsent(string source = null, string context = null, string action = null,
bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
bool includeHistory = false)
{
using (ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IConsent>();
if (string.IsNullOrWhiteSpace(source) == false)
query = sourceStartsWith ? query.Where(x => x.Source.StartsWith(source)) : query.Where(x => x.Source == source);
if (string.IsNullOrWhiteSpace(context) == false)
query = contextStartsWith ? query.Where(x => x.Context.StartsWith(context)) : query.Where(x => x.Context == context);
if (string.IsNullOrWhiteSpace(action) == false)
query = actionStartsWith ? query.Where(x => x.Action.StartsWith(action)) : query.Where(x => x.Action == action);
if (includeHistory == false)
query = query.Where(x => x.Current);
return _consentRepository.Get(query);
}
}
}
}

View File

@@ -63,7 +63,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.ContentTree);
return _documentRepository.CountPublished();
return _documentRepository.CountPublished(contentTypeAlias);
}
}
@@ -651,7 +651,7 @@ namespace Umbraco.Core.Services.Implement
totalChildren = 0;
return Enumerable.Empty<IContent>();
}
query.Where(x => x.Path.SqlStartsWith($"{contentPath[0]},", TextColumnType.NVarchar));
query.Where(x => x.Path.SqlStartsWith($"{contentPath[0].Path},", TextColumnType.NVarchar));
}
return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter);
}
@@ -800,8 +800,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.ContentTree);
var bin = $"{Constants.System.Root},{Constants.System.RecycleBinContent},";
var query = Query<IContent>().Where(x => x.Path.StartsWith(bin));
var query = Query<IContent>().Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix));
return _documentRepository.Get(query);
}
}
@@ -1709,7 +1708,7 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Sorts a collection of <see cref="IContent"/> objects by updating the SortOrder according
/// to the ordering of items in the passed in <see cref="IEnumerable{T}"/>.
/// to the ordering of items in the passed in <paramref name="items"/>.
/// </summary>
/// <remarks>
/// Using this method will ensure that the Published-state is maintained upon sorting
@@ -1726,56 +1725,88 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IContent>(itemsA);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
return false;
var published = new List<IContent>();
var saved = new List<IContent>();
scope.WriteLock(Constants.Locks.ContentTree);
var sortOrder = 0;
foreach (var content in itemsA)
{
// if the current sort order equals that of the content we don't
// need to update it, so just increment the sort order and continue.
if (content.SortOrder == sortOrder)
{
sortOrder++;
continue;
}
// else update
content.SortOrder = sortOrder++;
content.WriterId = userId;
// if it's published, register it, no point running StrategyPublish
// since we're not really publishing it and it cannot be cancelled etc
if (content.Published)
published.Add(content);
// save
saved.Add(content);
_documentRepository.Save(content);
}
if (raiseEvents)
{
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
}
scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.RefreshNode)).ToEventArgs());
if (raiseEvents && published.Any())
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(published, false, false), "Published");
Audit(AuditType.Sort, "Sorting content performed by user", userId, 0);
var ret = Sort(scope, itemsA, userId, raiseEvents);
scope.Complete();
return ret;
}
}
/// <summary>
/// Sorts a collection of <see cref="IContent"/> objects by updating the SortOrder according
/// to the ordering of items identified by the <paramref name="ids"/>.
/// </summary>
/// <remarks>
/// Using this method will ensure that the Published-state is maintained upon sorting
/// so the cache is updated accordingly - as needed.
/// </remarks>
/// <param name="ids"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <returns>True if sorting succeeded, otherwise False</returns>
public bool Sort(IEnumerable<int> ids, int userId = 0, bool raiseEvents = true)
{
var idsA = ids.ToArray();
if (idsA.Length == 0) return true;
using (var scope = ScopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.ContentTree);
var itemsA = GetByIds(idsA).ToArray();
var ret = Sort(scope, itemsA, userId, raiseEvents);
scope.Complete();
return ret;
}
}
private bool Sort(IScope scope, IContent[] itemsA, int userId, bool raiseEvents)
{
var saveEventArgs = new SaveEventArgs<IContent>(itemsA);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
return false;
var published = new List<IContent>();
var saved = new List<IContent>();
var sortOrder = 0;
foreach (var content in itemsA)
{
// if the current sort order equals that of the content we don't
// need to update it, so just increment the sort order and continue.
if (content.SortOrder == sortOrder)
{
sortOrder++;
continue;
}
// else update
content.SortOrder = sortOrder++;
content.WriterId = userId;
// if it's published, register it, no point running StrategyPublish
// since we're not really publishing it and it cannot be cancelled etc
if (content.Published)
published.Add(content);
// save
saved.Add(content);
_documentRepository.Save(content);
}
if (raiseEvents)
{
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
}
scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.RefreshNode)).ToEventArgs());
if (raiseEvents && published.Any())
scope.Events.Dispatch(Published, this, new PublishEventArgs<IContent>(published, false, false), "Published");
Audit(AuditType.Sort, "Sorting content performed by user", userId, 0);
return true;
}
@@ -2302,6 +2333,38 @@ namespace Umbraco.Core.Services.Implement
}
}
public void DeleteBlueprintsOfTypes(IEnumerable<int> contentTypeIds, int userId = 0)
{
using (var scope = ScopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.ContentTree);
var contentTypeIdsA = contentTypeIds.ToArray();
var query = Query<IContent>();
if (contentTypeIdsA.Length > 0)
query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId));
var blueprints = _documentBlueprintRepository.Get(query).Select(x =>
{
((Content) x).Blueprint = true;
return x;
}).ToArray();
foreach (var blueprint in blueprints)
{
_documentBlueprintRepository.Delete(blueprint);
}
scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs<IContent>(blueprints), "DeletedBlueprint");
scope.Complete();
}
}
public void DeleteBlueprintsOfType(int contentTypeId, int userId = 0)
{
DeleteBlueprintsOfTypes(new[] { contentTypeId }, userId);
}
#endregion
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
@@ -32,8 +33,13 @@ namespace Umbraco.Core.Services.Implement
protected override void DeleteItemsOfTypes(IEnumerable<int> typeIds)
{
foreach (var typeId in typeIds)
ContentService.DeleteOfType(typeId);
using (var scope = ScopeProvider.CreateScope())
{
var typeIdsA = typeIds.ToArray();
ContentService.DeleteOfTypes(typeIdsA);
ContentService.DeleteBlueprintsOfTypes(typeIdsA);
scope.Complete();
}
}
/// <summary>
@@ -82,6 +88,5 @@ namespace Umbraco.Core.Services.Implement
return Repository.GetAllContentTypeIds(aliases);
}
}
}
}

View File

@@ -130,7 +130,7 @@ namespace Umbraco.Core.Services.Implement
protected void OnDeletedContainer(IScope scope, DeleteEventArgs<EntityContainer> args)
{
scope.Events.Dispatch(DeletedContainer, This, args);
scope.Events.Dispatch(DeletedContainer, This, args, "DeletedContainer");
}
}
}

View File

@@ -445,7 +445,7 @@ namespace Umbraco.Core.Services.Implement
//null check otherwise we get exceptions
if (media.Path.IsNullOrWhiteSpace()) return Enumerable.Empty<IMedia>();
var rootId = Constants.System.Root.ToInvariantString();
var rootId = Constants.System.RootString;
var ids = media.Path.Split(',')
.Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture))
.Select(int.Parse)
@@ -616,7 +616,7 @@ namespace Umbraco.Core.Services.Implement
totalChildren = 0;
return Enumerable.Empty<IMedia>();
}
query.Where(x => x.Path.SqlStartsWith(mediaPath[0] + ",", TextColumnType.NVarchar));
query.Where(x => x.Path.SqlStartsWith(mediaPath[0].Path + ",", TextColumnType.NVarchar));
}
return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter);
}
@@ -706,8 +706,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MediaTree);
var bin = $"{Constants.System.Root},{Constants.System.RecycleBinMedia},";
var query = Query<IMedia>().Where(x => x.Path.StartsWith(bin));
var query = Query<IMedia>().Where(x => x.Path.StartsWith(Constants.System.RecycleBinMediaPathPrefix));
return _mediaRepository.Get(query);
}
}
@@ -734,7 +733,7 @@ namespace Umbraco.Core.Services.Implement
/// <returns><see cref="IMedia"/></returns>
public IMedia GetMediaByPath(string mediaPath)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
using (ScopeProvider.CreateScope(autoComplete: true))
{
return _mediaRepository.GetMediaByPath(mediaPath);
}

View File

@@ -403,7 +403,7 @@ namespace Umbraco.Core.Services.Implement
{
scope.ReadLock(Constants.Locks.MemberTree);
var query1 = memberTypeAlias == null ? null : Query<IMember>().Where(x => x.ContentTypeAlias == memberTypeAlias);
var query2 = filter == null ? null : Query<IMember>().Where(x => x.Name.Contains(filter) || x.Username.Contains(filter));
var query2 = filter == null ? null : Query<IMember>().Where(x => x.Name.Contains(filter) || x.Username.Contains(filter) || x.Email.Contains(filter));
return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, query2);
}
}
@@ -815,6 +815,10 @@ namespace Umbraco.Core.Services.Implement
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IMember member, bool raiseEvents = true)
{
//trimming username and email to make sure we have no trailing space
member.Username = member.Username.Trim();
member.Email = member.Email.Trim();
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IMember>(member);
@@ -866,7 +870,13 @@ namespace Umbraco.Core.Services.Implement
scope.WriteLock(Constants.Locks.MemberTree);
foreach (var member in membersA)
{
//trimming username and email to make sure we have no trailing space
member.Username = member.Username.Trim();
member.Email = member.Email.Trim();
_memberRepository.Save(member);
}
if (raiseEvents)
{
@@ -1018,7 +1028,9 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.MemberTree);
_memberGroupRepository.AssignRoles(usernames, roleNames);
var ids = _memberGroupRepository.GetMemberIds(usernames);
_memberGroupRepository.AssignRoles(ids, roleNames);
scope.Events.Dispatch(AssignedRoles, this, new RolesEventArgs(ids, roleNames));
scope.Complete();
}
}
@@ -1033,7 +1045,9 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.MemberTree);
_memberGroupRepository.DissociateRoles(usernames, roleNames);
var ids = _memberGroupRepository.GetMemberIds(usernames);
_memberGroupRepository.DissociateRoles(ids, roleNames);
scope.Events.Dispatch(RemovedRoles, this, new RolesEventArgs(ids, roleNames));
scope.Complete();
}
}
@@ -1049,6 +1063,7 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.MemberTree);
_memberGroupRepository.AssignRoles(memberIds, roleNames);
scope.Events.Dispatch(AssignedRoles, this, new RolesEventArgs(memberIds, roleNames));
scope.Complete();
}
}
@@ -1064,6 +1079,7 @@ namespace Umbraco.Core.Services.Implement
{
scope.WriteLock(Constants.Locks.MemberTree);
_memberGroupRepository.DissociateRoles(memberIds, roleNames);
scope.Events.Dispatch(RemovedRoles, this, new RolesEventArgs(memberIds, roleNames));
scope.Complete();
}
}
@@ -1110,6 +1126,21 @@ namespace Umbraco.Core.Services.Implement
/// </summary>
public static event TypedEventHandler<IMemberService, SaveEventArgs<IMember>> Saved;
/// <summary>
/// Occurs after roles have been assigned.
/// </summary>
public static event TypedEventHandler<IMemberService, RolesEventArgs> AssignedRoles;
/// <summary>
/// Occurs after roles have been removed.
/// </summary>
public static event TypedEventHandler<IMemberService, RolesEventArgs> RemovedRoles;
/// <summary>
/// Occurs after members have been exported.
/// </summary>
internal static event TypedEventHandler<IMemberService, ExportedMemberEventArgs> Exported;
#endregion
#region Membership
@@ -1219,6 +1250,72 @@ namespace Umbraco.Core.Services.Implement
return member;
}
/// <summary>
/// Exports a member.
/// </summary>
/// <remarks>
/// This is internal for now and is used to export a member in the member editor,
/// it will raise an event so that auditing logs can be created.
/// </remarks>
internal MemberExportModel ExportMember(Guid key)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IMember>().Where(x => x.Key == key);
var member = _memberRepository.Get(query).FirstOrDefault();
if (member == null) return null;
var model = new MemberExportModel
{
Id = member.Id,
Key = member.Key,
Name = member.Name,
Username = member.Username,
Email = member.Email,
Groups = GetAllRoles(member.Id).ToList(),
ContentTypeAlias = member.ContentTypeAlias,
CreateDate = member.CreateDate,
UpdateDate = member.UpdateDate,
Properties = new List<MemberExportProperty>(GetPropertyExportItems(member))
};
scope.Events.Dispatch(Exported, this, new ExportedMemberEventArgs(member, model));
return model;
}
}
private static IEnumerable<MemberExportProperty> GetPropertyExportItems(IMember member)
{
if (member == null) throw new ArgumentNullException(nameof(member));
var exportProperties = new List<MemberExportProperty>();
foreach (var property in member.Properties)
{
//ignore list
switch (property.Alias)
{
case Constants.Conventions.Member.PasswordQuestion:
continue;
}
var propertyExportModel = new MemberExportProperty
{
Id = property.Id,
Alias = property.Alias,
Name = property.PropertyType.Name,
Value = property.GetValue(), // fixme ignoring variants
CreateDate = property.CreateDate,
UpdateDate = property.UpdateDate
};
exportProperties.Add(propertyExportModel);
}
return exportProperties;
}
#endregion
#region Content Types

View File

@@ -768,21 +768,18 @@ namespace Umbraco.Core.Services.Implement
foreach (var element in structureElement.Elements("DocumentType"))
{
var alias = element.Value;
if (_importedContentTypes.ContainsKey(alias))
{
var allowedChild = _importedContentTypes[alias];
if (allowedChild == null || allowedChildren.Any(x => x.Id.IsValueCreated && x.Id.Value == allowedChild.Id)) continue;
allowedChildren.Add(new ContentTypeSort(new Lazy<int>(() => allowedChild.Id), sortOrder, allowedChild.Alias));
sortOrder++;
}
else
var allowedChild = _importedContentTypes.ContainsKey(alias) ? _importedContentTypes[alias] : _contentTypeService.Get(alias);
if (allowedChild == null)
{
_logger.Warn<PackagingService>(
string.Format(
"Packager: Error handling DocumentType structure. DocumentType with alias '{0}' could not be found and was not added to the structure for '{1}'.",
alias, contentType.Alias));
_logger.Warn<PackagingService>($"Packager: Error handling DocumentType structure. DocumentType with alias '{alias}' could not be found and was not added to the structure for '{contentType.Alias}'.");
continue;
}
if (allowedChildren.Any(x => x.Id.IsValueCreated && x.Id.Value == allowedChild.Id)) continue;
allowedChildren.Add(new ContentTypeSort(new Lazy<int>(() => allowedChild.Id), sortOrder, allowedChild.Alias));
sortOrder++;
}
contentType.AllowedContentTypes = allowedChildren;
@@ -1679,9 +1676,10 @@ namespace Umbraco.Core.Services.Implement
internal InstallationSummary InstallPackage(string packageFilePath, int userId = 0, bool raiseEvents = false)
{
var metaData = GetPackageMetaData(packageFilePath);
if (raiseEvents)
{
var metaData = GetPackageMetaData(packageFilePath);
if (ImportingPackage.IsRaisedEventCancelled(new ImportPackageEventArgs<string>(packageFilePath, metaData), this))
{
var initEmpty = new InstallationSummary().InitEmpty();
@@ -1693,7 +1691,7 @@ namespace Umbraco.Core.Services.Implement
if (raiseEvents)
{
ImportedPackage.RaiseEvent(new ImportPackageEventArgs<InstallationSummary>(installationSummary, false), this);
ImportedPackage.RaiseEvent(new ImportPackageEventArgs<InstallationSummary>(installationSummary, metaData, false), this);
}
return installationSummary;

View File

@@ -224,9 +224,9 @@ namespace Umbraco.Core.Services.Implement
}
/// <summary>
/// Deletes an <see cref="IUser"/>
/// Disables an <see cref="IUser"/>
/// </summary>
/// <param name="membershipUser"><see cref="IUser"/> to Delete</param>
/// <param name="membershipUser"><see cref="IUser"/> to disable</param>
public void Delete(IUser membershipUser)
{
//disable
@@ -542,6 +542,42 @@ namespace Umbraco.Core.Services.Implement
}
}
public Guid CreateLoginSession(int userId, string requestingIpAddress)
{
using (var scope = ScopeProvider.CreateScope())
{
var session = _userRepository.CreateLoginSession(userId, requestingIpAddress);
scope.Complete();
return session;
}
}
public int ClearLoginSessions(int userId)
{
using (var scope = ScopeProvider.CreateScope())
{
var count = _userRepository.ClearLoginSessions(userId);
scope.Complete();
return count;
}
}
public void ClearLoginSession(Guid sessionId)
{
using (var scope = ScopeProvider.CreateScope())
{
_userRepository.ClearLoginSession(sessionId);
scope.Complete();
}
}
public bool ValidateLoginSession(int userId, Guid sessionId)
{
using (ScopeProvider.CreateScope(autoComplete: true))
{
return _userRepository.ValidateLoginSession(userId, sessionId);
}
}
public IDictionary<UserState, int> GetUserStates()
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -747,8 +783,9 @@ namespace Umbraco.Core.Services.Implement
_userGroupRepository.ReplaceGroupPermissions(groupId, permissions, entityIds);
scope.Complete();
scope.Events.Dispatch(UserGroupPermissionsAssigned, this, new SaveEventArgs<EntityPermission>(entityIds.Select(x => new EntityPermission(groupId, x, permissions.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray()))
.ToArray(), false));
var assigned = permissions.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray();
scope.Events.Dispatch(UserGroupPermissionsAssigned, this,
new SaveEventArgs<EntityPermission>(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false));
}
}
@@ -768,8 +805,9 @@ namespace Umbraco.Core.Services.Implement
_userGroupRepository.AssignGroupPermission(groupId, permission, entityIds);
scope.Complete();
scope.Events.Dispatch(UserGroupPermissionsAssigned, this, new SaveEventArgs<EntityPermission>(entityIds.Select(x => new EntityPermission(groupId, x, new[] { permission.ToString(CultureInfo.InvariantCulture) }))
.ToArray(), false));
var assigned = new[] { permission.ToString(CultureInfo.InvariantCulture) };
scope.Events.Dispatch(UserGroupPermissionsAssigned, this,
new SaveEventArgs<EntityPermission>(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false));
}
}
@@ -842,7 +880,23 @@ namespace Umbraco.Core.Services.Implement
{
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IUserGroup>(userGroup);
// we need to figure out which users have been added / removed, for audit purposes
var empty = new IUser[0];
var addedUsers = empty;
var removedUsers = empty;
if (userIds != null)
{
var groupUsers = userGroup.HasIdentity ? _userRepository.GetAllInGroup(userGroup.Id).ToArray() : empty;
var xGroupUsers = groupUsers.ToDictionary(x => x.Id, x => x);
var groupIds = groupUsers.Select(x => x.Id).ToArray();
addedUsers = _userRepository.GetMany(userIds.Except(groupIds).ToArray()).Where(x => x.Id != 0).ToArray();
removedUsers = groupIds.Except(userIds).Select(x => xGroupUsers[x]).Where(x => x.Id != 0).ToArray();
}
var saveEventArgs = new SaveEventArgs<UserGroupWithUsers>(new UserGroupWithUsers(userGroup, addedUsers, removedUsers));
if (raiseEvents && scope.Events.DispatchCancelable(SavingUserGroup, this, saveEventArgs))
{
scope.Complete();
@@ -1183,12 +1237,12 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<IUserGroup>> SavingUserGroup;
internal static event TypedEventHandler<IUserService, SaveEventArgs<UserGroupWithUsers>> SavingUserGroup;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<IUserGroup>> SavedUserGroup;
internal static event TypedEventHandler<IUserService, SaveEventArgs<UserGroupWithUsers>> SavedUserGroup;
/// <summary>
/// Occurs before Delete

View File

@@ -34,12 +34,13 @@ namespace Umbraco.Core.Services
private readonly Lazy<INotificationService> _notificationService;
private readonly Lazy<IExternalLoginService> _externalLoginService;
private readonly Lazy<IRedirectUrlService> _redirectUrlService;
private readonly Lazy<IConsentService> _consentService;
/// <summary>
/// Initializes a new instance of the <see cref="ServiceContext"/> class with lazy services.
/// </summary>
/// <remarks>Used by IoC. Note that LightInject will favor lazy args when picking a constructor.</remarks>
public ServiceContext(Lazy<IPublicAccessService> publicAccessService, Lazy<ITaskService> taskService, Lazy<IDomainService> domainService, Lazy<IAuditService> auditService, Lazy<ILocalizedTextService> localizedTextService, Lazy<ITagService> tagService, Lazy<IContentService> contentService, Lazy<IUserService> userService, Lazy<IMemberService> memberService, Lazy<IMediaService> mediaService, Lazy<IContentTypeService> contentTypeService, Lazy<IMediaTypeService> mediaTypeService, Lazy<IDataTypeService> dataTypeService, Lazy<IFileService> fileService, Lazy<ILocalizationService> localizationService, Lazy<IPackagingService> packagingService, Lazy<IServerRegistrationService> serverRegistrationService, Lazy<IEntityService> entityService, Lazy<IRelationService> relationService, Lazy<IApplicationTreeService> treeService, Lazy<ISectionService> sectionService, Lazy<IMacroService> macroService, Lazy<IMemberTypeService> memberTypeService, Lazy<IMemberGroupService> memberGroupService, Lazy<INotificationService> notificationService, Lazy<IExternalLoginService> externalLoginService, Lazy<IRedirectUrlService> redirectUrlService)
public ServiceContext(Lazy<IPublicAccessService> publicAccessService, Lazy<ITaskService> taskService, Lazy<IDomainService> domainService, Lazy<IAuditService> auditService, Lazy<ILocalizedTextService> localizedTextService, Lazy<ITagService> tagService, Lazy<IContentService> contentService, Lazy<IUserService> userService, Lazy<IMemberService> memberService, Lazy<IMediaService> mediaService, Lazy<IContentTypeService> contentTypeService, Lazy<IMediaTypeService> mediaTypeService, Lazy<IDataTypeService> dataTypeService, Lazy<IFileService> fileService, Lazy<ILocalizationService> localizationService, Lazy<IPackagingService> packagingService, Lazy<IServerRegistrationService> serverRegistrationService, Lazy<IEntityService> entityService, Lazy<IRelationService> relationService, Lazy<IApplicationTreeService> treeService, Lazy<ISectionService> sectionService, Lazy<IMacroService> macroService, Lazy<IMemberTypeService> memberTypeService, Lazy<IMemberGroupService> memberGroupService, Lazy<INotificationService> notificationService, Lazy<IExternalLoginService> externalLoginService, Lazy<IRedirectUrlService> redirectUrlService, Lazy<IConsentService> consentService)
{
_publicAccessService = publicAccessService;
_taskService = taskService;
@@ -68,14 +69,14 @@ namespace Umbraco.Core.Services
_notificationService = notificationService;
_externalLoginService = externalLoginService;
_redirectUrlService = redirectUrlService;
_consentService = consentService;
}
/// <summary>
/// Initializes a new instance of the <see cref="ServiceContext"/> class with services.
/// </summary>
/// <remarks>Used in tests. All items are optional and remain null if not specified.</remarks>
public ServiceContext(
IContentService contentService = null,
public ServiceContext(IContentService contentService = null,
IMediaService mediaService = null,
IContentTypeService contentTypeService = null,
IMediaTypeService mediaTypeService = null,
@@ -101,7 +102,8 @@ namespace Umbraco.Core.Services
IPublicAccessService publicAccessService = null,
IExternalLoginService externalLoginService = null,
IServerRegistrationService serverRegistrationService = null,
IRedirectUrlService redirectUrlService = null)
IRedirectUrlService redirectUrlService = null,
IConsentService consentService = null)
{
if (serverRegistrationService != null) _serverRegistrationService = new Lazy<IServerRegistrationService>(() => serverRegistrationService);
if (externalLoginService != null) _externalLoginService = new Lazy<IExternalLoginService>(() => externalLoginService);
@@ -130,6 +132,7 @@ namespace Umbraco.Core.Services
if (macroService != null) _macroService = new Lazy<IMacroService>(() => macroService);
if (publicAccessService != null) _publicAccessService = new Lazy<IPublicAccessService>(() => publicAccessService);
if (redirectUrlService != null) _redirectUrlService = new Lazy<IRedirectUrlService>(() => redirectUrlService);
if (consentService != null) _consentService = new Lazy<IConsentService>(() => consentService);
}
/// <summary>
@@ -257,11 +260,19 @@ namespace Umbraco.Core.Services
/// </summary>
public IMemberGroupService MemberGroupService => _memberGroupService.Value;
/// <summary>
/// Gets the ExternalLoginService.
/// </summary>
public IExternalLoginService ExternalLoginService => _externalLoginService.Value;
/// <summary>
/// Gets the RedirectUrlService.
/// </summary>
public IRedirectUrlService RedirectUrlService => _redirectUrlService.Value;
/// <summary>
/// Gets the ConsentService.
/// </summary>
public IConsentService ConsentService => _consentService.Value;
}
}

View File

@@ -132,7 +132,7 @@ namespace Umbraco.Core
public static GuidUdi GetUdi(this IContent entity)
{
if (entity == null) throw new ArgumentNullException("entity");
return new GuidUdi(entity.Blueprint ? Constants.UdiEntityType.DocumentBluePrint : Constants.UdiEntityType.Document, entity.Key).EnsureClosed();
return new GuidUdi(entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, entity.Key).EnsureClosed();
}
/// <summary>

View File

@@ -98,7 +98,7 @@
<Compile Include="Attempt.cs" />
<Compile Include="AttemptOfTResult.cs" />
<Compile Include="AttemptOfTResultTStatus.cs" />
<Compile Include="Auditing\AuditEventHandler.cs" />
<Compile Include="Auditing\AuditEventsComponent.cs" />
<Compile Include="Auditing\IdentityAuditEventArgs.cs" />
<Compile Include="BindingRedirects.cs" />
<Compile Include="ByteArrayExtensions.cs" />
@@ -377,6 +377,7 @@
<Compile Include="Persistence\Repositories\IConsentRepository.cs" />
<Compile Include="Persistence\Repositories\Implement\AuditEntryRepository.cs" />
<Compile Include="Persistence\Repositories\Implement\ConsentRepository.cs" />
<Compile Include="PropertyEditors\ColorPickerConfiguration.cs" />
<Compile Include="PropertyEditors\ConfigurationEditorOfTConfiguration.cs" />
<Compile Include="PropertyEditors\DataEditorAttribute.cs" />
<Compile Include="PropertyEditors\ConfigurationEditor.cs" />
@@ -395,6 +396,7 @@
<Compile Include="PropertyEditors\TagConfiguration.cs" />
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValue.cs" />
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValueTypeConverter.cs" />
<Compile Include="PropertyEditors\ValueListConfiguration.cs" />
<Compile Include="PropertyEditors\VoidEditor.cs" />
<Compile Include="ReflectionUtilities-Unused.cs" />
<Compile Include="Runtime\CoreRuntime.cs" />
@@ -1349,6 +1351,7 @@
<Compile Include="Serialization\UdiJsonConverter.cs" />
<Compile Include="Serialization\UdiRangeJsonConverter.cs" />
<Compile Include="ServiceContextExtensions.cs" />
<Compile Include="Services\IConsentService.cs" />
<Compile Include="Services\Implement\AuditService.cs" />
<Compile Include="Services\Changes\ContentTypeChange.cs" />
<Compile Include="Services\Changes\ContentTypeChangeExtensions.cs" />
@@ -1357,6 +1360,7 @@
<Compile Include="Services\Changes\TreeChange.cs" />
<Compile Include="Services\Changes\TreeChangeExtensions.cs" />
<Compile Include="Services\Changes\TreeChangeTypes.cs" />
<Compile Include="Services\Implement\ConsentService.cs" />
<Compile Include="Services\Implement\ContentService.cs" />
<Compile Include="Services\ContentServiceExtensions.cs" />
<Compile Include="Services\Implement\ContentTypeService.cs" />

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -152,7 +153,7 @@ namespace Umbraco.Core
}
catch (ArgumentException ex)
{
LogHelper.Error(typeof(UriExtensions), "Failed to determine if request was client side", ex);
Current.Logger.Error(typeof(UriExtensions), "Failed to determine if request was client side", ex);
return false;
}
}

View File

@@ -2,6 +2,7 @@
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
@@ -9,12 +10,11 @@ using Umbraco.Core.Services;
namespace Umbraco.Web.PropertyEditors
{
internal class ColorPickerConfigurationEditor : ValueListConfigurationEditor
internal class ColorPickerConfigurationEditor : ConfigurationEditor<ColorPickerConfiguration>
{
public ColorPickerConfigurationEditor(ILocalizedTextService textService)
: base(textService)
public ColorPickerConfigurationEditor()
{
var field = Fields.First();
var field = Fields.First(x => x.Key == "items");
//use a custom editor too
field.View = "views/propertyeditors/colorpicker/colorpicker.prevalues.html";
@@ -26,23 +26,84 @@ namespace Umbraco.Web.PropertyEditors
field.Validators.Add(new ColorListValidator());
}
public override Dictionary<string, object> ToConfigurationEditor(ValueListConfiguration configuration)
public override Dictionary<string, object> ToConfigurationEditor(ColorPickerConfiguration configuration)
{
if (configuration == null)
return new Dictionary<string, object>
{
{ "items", new object() }
{ "items", new object() },
{ "useLabel", false }
};
// for now, we have to do this, because the color picker is weird, but it's fixed in 7.7 at some point
// and then we probably don't need this whole override method anymore - base shouls be enough?
var items = configuration.Items.ToDictionary(x => x.Id.ToString(), x => GetItemValue(x, configuration.UseLabel));
return new Dictionary<string, object>
{
{ "items", configuration.Items.ToDictionary(x => x.Id.ToString(), x => x.Value) }
{ "items", items },
{ "useLabel", configuration.UseLabel }
};
}
private object GetItemValue(ValueListConfiguration.ValueListItem item, bool useLabel)
{
if (useLabel)
{
return item.Value.DetectIsJson()
? JsonConvert.DeserializeObject(item.Value)
: new JObject { { "color", item.Value }, { "label", item.Value } };
}
if (!item.Value.DetectIsJson())
return item.Value;
var jobject = (JObject) JsonConvert.DeserializeObject(item.Value);
return jobject.Property("color").Value.Value<string>();
}
public override ColorPickerConfiguration FromConfigurationEditor(Dictionary<string, object> editorValues, ColorPickerConfiguration configuration)
{
var output = new ColorPickerConfiguration();
if (!editorValues.TryGetValue("items", out var jjj) || !(jjj is JArray jItems))
return output; // oops
// handle useLabel
if (editorValues.TryGetValue("useLabel", out var useLabelObj))
output.UseLabel = useLabelObj.TryConvertTo<bool>();
// auto-assigning our ids, get next id from existing values
var nextId = 1;
if (configuration?.Items != null && configuration.Items.Count > 0)
nextId = configuration.Items.Max(x => x.Id) + 1;
// create ValueListItem instances - sortOrder is ignored here
foreach (var item in jItems.OfType<JObject>())
{
var value = item.Property("value")?.Value?.Value<string>();
if (string.IsNullOrWhiteSpace(value)) continue;
var id = item.Property("id")?.Value?.Value<int>() ?? 0;
if (id >= nextId) nextId = id + 1;
// if using a label, replace color by json blob
// (a pity we have to serialize here!)
if (output.UseLabel)
{
var label = item.Property("label")?.Value?.Value<string>();
value = JsonConvert.SerializeObject(new { value, label });
}
output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value });
}
// ensure ids
foreach (var item in output.Items)
if (item.Id == 0)
item.Id = nextId++;
return output;
}
internal class ColorListValidator : IValueValidator
{
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)

View File

@@ -298,7 +298,6 @@
<Compile Include="PropertyEditors\ValueConverters\MediaPickerValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\MemberPickerValueConverter.cs" />
<Compile Include="PropertyEditors\ValueConverters\MultiNodeTreePickerValueConverter.cs" />
<Compile Include="PropertyEditors\ValueListConfiguration.cs" />
<Compile Include="PropertyEditors\ValueListUniqueValueValidator.cs" />
<Compile Include="PublishedCache\NuCache\DataSource\PropertyData.cs" />
<Compile Include="PublishedCache\NuCache\NuCacheComponent.cs" />