porting 7.6@5c5baca into 8
This commit is contained in:
@@ -116,6 +116,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
internal CommaDelimitedConfigurationElement DisallowedUploadFiles
|
||||
{
|
||||
get { return GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("allowedUploadFiles")]
|
||||
internal CommaDelimitedConfigurationElement AllowedUploadFiles
|
||||
{
|
||||
get { return GetOptionalDelimitedElement("allowedUploadFiles", new string[0]); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("cloneXmlContent")]
|
||||
@@ -263,6 +269,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
IEnumerable<string> IContentSection.DisallowedUploadFiles
|
||||
{
|
||||
get { return DisallowedUploadFiles; }
|
||||
}
|
||||
|
||||
IEnumerable<string> IContentSection.AllowedUploadFiles
|
||||
{
|
||||
get { return AllowedUploadFiles; }
|
||||
}
|
||||
|
||||
bool IContentSection.CloneXmlContent
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public static class ContentSectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if file extension is allowed for upload based on (optional) white list and black list
|
||||
/// held in settings.
|
||||
/// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted.
|
||||
/// </summary>
|
||||
public static bool IsFileAllowedForUpload(this IContentSection contentSection, string extension)
|
||||
{
|
||||
return contentSection.AllowedUploadFiles.Any(x => x.InvariantEquals(extension)) ||
|
||||
(contentSection.AllowedUploadFiles.Any() == false &&
|
||||
contentSection.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
IEnumerable<string> DisallowedUploadFiles { get; }
|
||||
|
||||
IEnumerable<string> AllowedUploadFiles { get; }
|
||||
|
||||
bool CloneXmlContent { get; }
|
||||
|
||||
bool GlobalPreviewStorageEnabled { get; }
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
Guid Id { get; }
|
||||
string RepositoryUrl { get; }
|
||||
string WebServiceUrl { get; }
|
||||
bool HasCustomWebServiceUrl { get; }
|
||||
bool HasCustomWebServiceUrl { get; }
|
||||
string RestApiUrl { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
public static class RepositoryConfigExtensions
|
||||
{
|
||||
//Our package repo
|
||||
private static readonly Guid RepoGuid = new Guid("65194810-1f85-11dd-bd0b-0800200c9a66");
|
||||
|
||||
public static IRepository GetDefault(this IRepositoriesSection repos)
|
||||
{
|
||||
var found = repos.Repositories.FirstOrDefault(x => x.Id == RepoGuid);
|
||||
if (found == null)
|
||||
throw new InvalidOperationException("No default package repository found with id " + RepoGuid);
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,9 +38,16 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
get
|
||||
{
|
||||
var prop = Properties["webserviceurl"];
|
||||
var repoUrl = this[prop] as ConfigurationElement;
|
||||
return (repoUrl != null && repoUrl.ElementInformation.IsPresent);
|
||||
return (string) prop.DefaultValue != (string) this[prop];
|
||||
}
|
||||
}
|
||||
|
||||
[ConfigurationProperty("restapiurl", DefaultValue = "https://our.umbraco.org/webapi/packages/v1")]
|
||||
public string RestApiUrl
|
||||
{
|
||||
get { return (string)base["restapiurl"]; }
|
||||
set { base["restapiurl"] = value; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
public const string UmbracoMigrationName = "Umbraco";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ namespace Umbraco.Core
|
||||
composition.Container.RegisterSingleton<IServerRegistrar>(f =>
|
||||
{
|
||||
if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled)
|
||||
return new ConfigServerRegistrar(UmbracoConfig.For.UmbracoSettings());
|
||||
return new ConfigServerRegistrar(UmbracoConfig.For.UmbracoSettings(), f.GetInstance<ILogger>());
|
||||
if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"]))
|
||||
return new SingleServerRegistrar(f.GetInstance<IRuntimeState>());
|
||||
return new DatabaseServerRegistrar(
|
||||
|
||||
@@ -1,85 +1,177 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Permissions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Event args for a strongly typed object that can support cancellation
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
|
||||
public class CancellableObjectEventArgs<T> : CancellableEventArgs, IEquatable<CancellableObjectEventArgs<T>>
|
||||
{
|
||||
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
/// <summary>
|
||||
/// Used as a base class for the generic type CancellableObjectEventArgs{T} so that we can get direct 'object' access to the underlying EventObject
|
||||
/// </summary>
|
||||
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
|
||||
public abstract class CancellableObjectEventArgs : CancellableEventArgs
|
||||
{
|
||||
protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
: base(canCancel, messages, additionalData)
|
||||
{
|
||||
{
|
||||
EventObject = eventObject;
|
||||
}
|
||||
|
||||
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages)
|
||||
protected CancellableObjectEventArgs(object eventObject, bool canCancel, EventMessages eventMessages)
|
||||
: base(canCancel, eventMessages)
|
||||
{
|
||||
EventObject = eventObject;
|
||||
}
|
||||
|
||||
public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages)
|
||||
protected CancellableObjectEventArgs(object eventObject, EventMessages eventMessages)
|
||||
: this(eventObject, true, eventMessages)
|
||||
{
|
||||
}
|
||||
|
||||
protected CancellableObjectEventArgs(object eventObject, bool canCancel)
|
||||
: base(canCancel)
|
||||
{
|
||||
EventObject = eventObject;
|
||||
}
|
||||
|
||||
protected CancellableObjectEventArgs(object eventObject)
|
||||
: this(eventObject, true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the object relating to the event
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is protected so that inheritors can expose it with their own name
|
||||
/// </remarks>
|
||||
internal object EventObject { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for a strongly typed object that can support cancellation
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
|
||||
public class CancellableObjectEventArgs<T> : CancellableObjectEventArgs, IEquatable<CancellableObjectEventArgs<T>>
|
||||
{
|
||||
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
: base(eventObject, canCancel, messages, additionalData)
|
||||
{
|
||||
}
|
||||
|
||||
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages)
|
||||
: base(eventObject, canCancel, eventMessages)
|
||||
{
|
||||
}
|
||||
|
||||
public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages)
|
||||
: base(eventObject, eventMessages)
|
||||
{
|
||||
}
|
||||
|
||||
public CancellableObjectEventArgs(T eventObject, bool canCancel)
|
||||
: base(canCancel)
|
||||
{
|
||||
EventObject = eventObject;
|
||||
}
|
||||
: base(eventObject, canCancel)
|
||||
{
|
||||
}
|
||||
|
||||
public CancellableObjectEventArgs(T eventObject)
|
||||
: this(eventObject, true)
|
||||
{
|
||||
}
|
||||
public CancellableObjectEventArgs(T eventObject)
|
||||
: base(eventObject)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the object relating to the event
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is protected so that inheritors can expose it with their own name
|
||||
/// </remarks>
|
||||
protected T EventObject { get; set; }
|
||||
/// <summary>
|
||||
/// Returns the object relating to the event
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is protected so that inheritors can expose it with their own name
|
||||
/// </remarks>
|
||||
protected new T EventObject
|
||||
{
|
||||
get { return (T) base.EventObject; }
|
||||
set { base.EventObject = value; }
|
||||
}
|
||||
|
||||
public bool Equals(CancellableObjectEventArgs<T> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && EqualityComparer<T>.Default.Equals(EventObject, other.EventObject);
|
||||
}
|
||||
public bool Equals(CancellableObjectEventArgs<T> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && EqualityComparer<T>.Default.Equals(EventObject, other.EventObject);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((CancellableObjectEventArgs<T>) obj);
|
||||
}
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((CancellableObjectEventArgs<T>)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ EqualityComparer<T>.Default.GetHashCode(EventObject);
|
||||
}
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ EqualityComparer<T>.Default.GetHashCode(EventObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
public static bool operator ==(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
public static bool operator !=(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
|
||||
public class CancellableEnumerableObjectEventArgs<T> : CancellableObjectEventArgs<IEnumerable<T>>, IEquatable<CancellableEnumerableObjectEventArgs<T>>
|
||||
{
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
: base(eventObject, canCancel, messages, additionalData)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel, EventMessages eventMessages)
|
||||
: base(eventObject, canCancel, eventMessages)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, EventMessages eventMessages)
|
||||
: base(eventObject, eventMessages)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject, bool canCancel)
|
||||
: base(eventObject, canCancel)
|
||||
{ }
|
||||
|
||||
public CancellableEnumerableObjectEventArgs(IEnumerable<T> eventObject)
|
||||
: base(eventObject)
|
||||
{ }
|
||||
|
||||
public bool Equals(CancellableEnumerableObjectEventArgs<T> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
return EventObject.SequenceEqual(other.EventObject);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((CancellableEnumerableObjectEventArgs<T>)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCodeHelper.GetHashCode(EventObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class DeleteEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<DeleteEventArgs<TEntity>>, IDeletingMediaFilesEventArgs
|
||||
[SupersedeEvent(typeof(SaveEventArgs<>))]
|
||||
[SupersedeEvent(typeof(PublishEventArgs<>))]
|
||||
[SupersedeEvent(typeof(MoveEventArgs<>))]
|
||||
[SupersedeEvent(typeof(CopyEventArgs<>))]
|
||||
public class DeleteEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<DeleteEventArgs<TEntity>>, IDeletingMediaFilesEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting multiple entities that are used in the delete operation
|
||||
@@ -106,7 +111,7 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && MediaFilesToDelete.Equals(other.MediaFilesToDelete);
|
||||
return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class ImportEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<ImportEventArgs<TEntity>>
|
||||
public class ImportEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<ImportEventArgs<TEntity>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting an XElement with the xml being imported
|
||||
|
||||
@@ -4,7 +4,7 @@ using Umbraco.Core.Models.Packaging;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal class ImportPackageEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<ImportPackageEventArgs<TEntity>>
|
||||
internal class ImportPackageEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<ImportPackageEventArgs<TEntity>>
|
||||
{
|
||||
private readonly MetaData _packageMetaData;
|
||||
|
||||
@@ -32,7 +32,8 @@ namespace Umbraco.Core.Events
|
||||
public bool Equals(ImportPackageEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
//TODO: MetaData for package metadata has no equality operators :/
|
||||
return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ using System.Linq;
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// This event manager supports event cancellation and will raise the events as soon as they are tracked, it does not store tracked events
|
||||
/// An IEventDispatcher that immediately raise all events.
|
||||
/// </summary>
|
||||
/// <remarks>This means that events will be raised during the scope transaction,
|
||||
/// whatever happens, and the transaction could roll back in the end.</remarks>
|
||||
internal class PassThroughEventDispatcher : IEventDispatcher
|
||||
{
|
||||
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class PublishEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<PublishEventArgs<TEntity>>
|
||||
public class PublishEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>, IEquatable<PublishEventArgs<TEntity>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting multiple entities that are used in the publish operation
|
||||
|
||||
@@ -4,11 +4,9 @@ using Umbraco.Core.IO;
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// This event manager is created for each scope and is aware of if it is nested in an outer scope
|
||||
/// An IEventDispatcher that queues events, and raise them when the scope
|
||||
/// exits and has been completed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The outer scope is the only scope that can raise events, the inner scope's will defer to the outer scope
|
||||
/// </remarks>
|
||||
internal class QueuingEventDispatcher : QueuingEventDispatcherBase
|
||||
{
|
||||
public QueuingEventDispatcher()
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Plugins;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// An IEventDispatcher that queues events.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Can raise, or ignore, cancelable events, depending on option.</para>
|
||||
/// <para>Implementations must override ScopeExitCompleted to define what
|
||||
/// to do with the events when the scope exits and has been completed.</para>
|
||||
/// <para>If the scope exits without being completed, events are ignored.</para>
|
||||
/// </remarks>
|
||||
public abstract class QueuingEventDispatcherBase : IEventDispatcher
|
||||
{
|
||||
//events will be enlisted in the order they are raised
|
||||
private List<IEventDefinition> _events;
|
||||
private readonly bool _raiseCancelable;
|
||||
|
||||
@@ -68,26 +80,256 @@ namespace Umbraco.Core.Events
|
||||
switch (filter)
|
||||
{
|
||||
case EventDefinitionFilter.All:
|
||||
return _events;
|
||||
return FilterSupersededAndUpdateToLatestEntity(_events);
|
||||
case EventDefinitionFilter.FirstIn:
|
||||
var l1 = new OrderedHashSet<IEventDefinition>();
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l1.Add(e);
|
||||
}
|
||||
return l1;
|
||||
return FilterSupersededAndUpdateToLatestEntity(l1);
|
||||
case EventDefinitionFilter.LastIn:
|
||||
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l2.Add(e);
|
||||
}
|
||||
return l2;
|
||||
return FilterSupersededAndUpdateToLatestEntity(l2);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(filter), filter, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class EventDefinitionTypeData
|
||||
{
|
||||
public IEventDefinition EventDefinition { get; set; }
|
||||
public Type EventArgType { get; set; }
|
||||
public SupersedeEventAttribute[] SupersedeAttributes { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will iterate over the events (latest first) and filter out any events or entities in event args that are included
|
||||
/// in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want
|
||||
/// to raise the Saved event (well actually we just don't want to include it in the args for that saved event)
|
||||
/// </summary>
|
||||
/// <param name="events"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<IEventDefinition> FilterSupersededAndUpdateToLatestEntity(IReadOnlyList<IEventDefinition> events)
|
||||
{
|
||||
//used to keep the 'latest' entity and associated event definition data
|
||||
var allEntities = new List<Tuple<IEntity, EventDefinitionTypeData>>();
|
||||
|
||||
//tracks all CancellableObjectEventArgs instances in the events which is the only type of args we can work with
|
||||
var cancelableArgs = new List<CancellableObjectEventArgs>();
|
||||
|
||||
var result = new List<IEventDefinition>();
|
||||
|
||||
//This will eagerly load all of the event arg types and their attributes so we don't have to continuously look this data up
|
||||
var allArgTypesWithAttributes = events.Select(x => x.Args.GetType())
|
||||
.Distinct()
|
||||
.ToDictionary(x => x, x => x.GetCustomAttributes<SupersedeEventAttribute>(false).ToArray());
|
||||
|
||||
//Iterate all events and collect the actual entities in them and relates them to their corresponding EventDefinitionTypeData
|
||||
//we'll process the list in reverse because events are added in the order they are raised and we want to filter out
|
||||
//any entities from event args that are not longer relevant
|
||||
//(i.e. if an item is Deleted after it's Saved, we won't include the item in the Saved args)
|
||||
for (var index = events.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var eventDefinition = events[index];
|
||||
|
||||
var argType = eventDefinition.Args.GetType();
|
||||
var attributes = allArgTypesWithAttributes[eventDefinition.Args.GetType()];
|
||||
|
||||
var meta = new EventDefinitionTypeData
|
||||
{
|
||||
EventDefinition = eventDefinition,
|
||||
EventArgType = argType,
|
||||
SupersedeAttributes = attributes
|
||||
};
|
||||
|
||||
var args = eventDefinition.Args as CancellableObjectEventArgs;
|
||||
if (args != null)
|
||||
{
|
||||
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
//extract the event object
|
||||
var obj = args.EventObject as IEntity;
|
||||
if (obj != null)
|
||||
{
|
||||
//Now check if this entity already exists in other event args that supersede this current event arg type
|
||||
if (IsFiltered(obj, meta, allEntities) == false)
|
||||
{
|
||||
//if it's not filtered we can adde these args to the response
|
||||
cancelableArgs.Add(args);
|
||||
result.Add(eventDefinition);
|
||||
//track the entity
|
||||
allEntities.Add(Tuple.Create(obj, meta));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Can't retrieve the entity so cant' filter or inspect, just add to the output
|
||||
result.Add(eventDefinition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var toRemove = new List<IEntity>();
|
||||
foreach (var entity in list)
|
||||
{
|
||||
//extract the event object
|
||||
var obj = entity as IEntity;
|
||||
if (obj != null)
|
||||
{
|
||||
//Now check if this entity already exists in other event args that supersede this current event arg type
|
||||
if (IsFiltered(obj, meta, allEntities))
|
||||
{
|
||||
//track it to be removed
|
||||
toRemove.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
//track the entity, it's not filtered
|
||||
allEntities.Add(Tuple.Create(obj, meta));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//we don't need to do anything here, we can't cast to IEntity so we cannot filter, so it will just remain in the list
|
||||
}
|
||||
}
|
||||
|
||||
//remove anything that has been filtered
|
||||
foreach (var entity in toRemove)
|
||||
{
|
||||
list.Remove(entity);
|
||||
}
|
||||
|
||||
//track the event and include in the response if there's still entities remaining in the list
|
||||
if (list.Count > 0)
|
||||
{
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
//re-assign if the items have changed
|
||||
args.EventObject = list;
|
||||
}
|
||||
cancelableArgs.Add(args);
|
||||
result.Add(eventDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//it's not a cancelable event arg so we just include it in the result
|
||||
result.Add(eventDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
|
||||
UpdateToLatestEntities(allEntities, cancelableArgs);
|
||||
|
||||
//we need to reverse the result since we've been adding by latest added events first!
|
||||
result.Reverse();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void UpdateToLatestEntities(IEnumerable<Tuple<IEntity, EventDefinitionTypeData>> allEntities, IEnumerable<CancellableObjectEventArgs> cancelableArgs)
|
||||
{
|
||||
//Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
|
||||
|
||||
var latestEntities = new OrderedHashSet<IEntity>(keepOldest: true);
|
||||
foreach (var entity in allEntities.OrderByDescending(entity => entity.Item1.UpdateDate))
|
||||
{
|
||||
latestEntities.Add(entity.Item1);
|
||||
}
|
||||
|
||||
foreach (var args in cancelableArgs)
|
||||
{
|
||||
var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);
|
||||
if (list == null)
|
||||
{
|
||||
//try to find the args entity in the latest entity - based on the equality operators, this will
|
||||
//match by Id since that is the default equality checker for IEntity. If one is found, than it is
|
||||
//the most recent entity instance so update the args with that instance so we don't emit a stale instance.
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, args.EventObject));
|
||||
if (foundEntity != null)
|
||||
{
|
||||
args.EventObject = foundEntity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var updated = false;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
//try to find the args entity in the latest entity - based on the equality operators, this will
|
||||
//match by Id since that is the default equality checker for IEntity. If one is found, than it is
|
||||
//the most recent entity instance so update the args with that instance so we don't emit a stale instance.
|
||||
var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, list[i]));
|
||||
if (foundEntity != null)
|
||||
{
|
||||
list[i] = foundEntity;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
{
|
||||
args.EventObject = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will check against all of the processed entity/events (allEntities) to see if this entity already exists in
|
||||
/// event args that supersede the event args being passed in and if so returns true.
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="eventDef"></param>
|
||||
/// <param name="allEntities"></param>
|
||||
/// <returns></returns>
|
||||
private static bool IsFiltered(
|
||||
IEntity entity,
|
||||
EventDefinitionTypeData eventDef,
|
||||
List<Tuple<IEntity, EventDefinitionTypeData>> allEntities)
|
||||
{
|
||||
var argType = eventDef.EventDefinition.Args.GetType();
|
||||
|
||||
//check if the entity is found in any processed event data that could possible supersede this one
|
||||
var foundByEntity = allEntities
|
||||
.Where(x => x.Item2.SupersedeAttributes.Length > 0
|
||||
//if it's the same arg type than it cannot supersede
|
||||
&& x.Item2.EventArgType != argType
|
||||
&& Equals(x.Item1, entity))
|
||||
.ToArray();
|
||||
|
||||
//no args have been processed with this entity so it should not be filtered
|
||||
if (foundByEntity.Length == 0)
|
||||
return false;
|
||||
|
||||
if (argType.IsGenericType)
|
||||
{
|
||||
var supercededBy = foundByEntity
|
||||
.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeAttributes.Any(y =>
|
||||
//if the attribute type is a generic type def then compare with the generic type def of the event arg
|
||||
(y.SupersededEventArgsType.IsGenericTypeDefinition && y.SupersededEventArgsType == argType.GetGenericTypeDefinition())
|
||||
//if the attribute type is not a generic type def then compare with the normal type of the event arg
|
||||
|| (y.SupersededEventArgsType.IsGenericTypeDefinition == false && y.SupersededEventArgsType == argType)));
|
||||
return supercededBy != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var supercededBy = foundByEntity
|
||||
.FirstOrDefault(x =>
|
||||
x.Item2.SupersedeAttributes.Any(y =>
|
||||
//since the event arg type is not a generic type, then we just compare type 1:1
|
||||
y.SupersededEventArgsType == argType));
|
||||
return supercededBy != null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ScopeExit(bool completed)
|
||||
{
|
||||
if (_events == null) return;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class SaveEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
|
||||
public class SaveEventArgs<TEntity> : CancellableEnumerableObjectEventArgs<TEntity>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting multiple entities that are used in the saving operation
|
||||
|
||||
20
src/Umbraco.Core/Events/SupersedeEventAttribute.cs
Normal file
20
src/Umbraco.Core/Events/SupersedeEventAttribute.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used to know if the event arg attributed should supersede another event arg type when
|
||||
/// tracking events for the same entity. If one event args supercedes another then the event args that have been superseded
|
||||
/// will mean that the event will not be dispatched or the args will be filtered to exclude the entity.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
internal class SupersedeEventAttribute : Attribute
|
||||
{
|
||||
public Type SupersededEventArgsType { get; private set; }
|
||||
|
||||
public SupersedeEventAttribute(Type supersededEventArgsType)
|
||||
{
|
||||
SupersededEventArgsType = supersededEventArgsType;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Umbraco.Core/Exceptions/ConnectionException.cs
Normal file
12
src/Umbraco.Core/Exceptions/ConnectionException.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Exceptions
|
||||
{
|
||||
internal class ConnectionException : Exception
|
||||
{
|
||||
public ConnectionException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace Umbraco.Core.Exceptions
|
||||
public T Operation { get; private set; }
|
||||
|
||||
public DataOperationException(T operation, string message)
|
||||
:base(message)
|
||||
: base(message)
|
||||
{
|
||||
Operation = operation;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -55,14 +52,14 @@ namespace Umbraco.Core
|
||||
AddCaseInsensitiveString(f.FullName);
|
||||
AddDateTime(f.CreationTimeUtc);
|
||||
AddDateTime(f.LastWriteTimeUtc);
|
||||
|
||||
//check if it is a file or folder
|
||||
|
||||
//check if it is a file or folder
|
||||
var fileInfo = f as FileInfo;
|
||||
if (fileInfo != null)
|
||||
{
|
||||
AddInt(fileInfo.Length.GetHashCode());
|
||||
}
|
||||
|
||||
|
||||
var dirInfo = f as DirectoryInfo;
|
||||
if (dirInfo != null)
|
||||
{
|
||||
@@ -79,12 +76,12 @@ namespace Umbraco.Core
|
||||
|
||||
internal void AddFile(FileInfo f)
|
||||
{
|
||||
AddFileSystemItem(f);
|
||||
AddFileSystemItem(f);
|
||||
}
|
||||
|
||||
internal void AddFolder(DirectoryInfo d)
|
||||
{
|
||||
AddFileSystemItem(d);
|
||||
AddFileSystemItem(d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
104
src/Umbraco.Core/HashCodeHelper.cs
Normal file
104
src/Umbraco.Core/HashCodeHelper.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Borrowed from http://stackoverflow.com/a/2575444/694494
|
||||
/// </summary>
|
||||
internal static class HashCodeHelper
|
||||
{
|
||||
public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return 31 * arg1.GetHashCode() + arg2.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = arg1.GetHashCode();
|
||||
hash = 31 * hash + arg2.GetHashCode();
|
||||
return 31 * hash + arg3.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3,
|
||||
T4 arg4)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = arg1.GetHashCode();
|
||||
hash = 31 * hash + arg2.GetHashCode();
|
||||
hash = 31 * hash + arg3.GetHashCode();
|
||||
return 31 * hash + arg4.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetHashCode<T>(T[] list)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 0;
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (item == null) continue;
|
||||
hash = 31 * hash + item.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetHashCode<T>(IEnumerable<T> list)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 0;
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (item == null) continue;
|
||||
hash = 31 * hash + item.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a hashcode for a collection for that the order of items
|
||||
/// does not matter.
|
||||
/// So {1, 2, 3} and {3, 2, 1} will get same hash code.
|
||||
/// </summary>
|
||||
public static int GetHashCodeForOrderNoMatterCollection<T>(
|
||||
IEnumerable<T> list)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 0;
|
||||
int count = 0;
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (item == null) continue;
|
||||
hash += item.GetHashCode();
|
||||
count++;
|
||||
}
|
||||
return 31 * hash + count.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alternative way to get a hashcode is to use a fluent
|
||||
/// interface like this:<br />
|
||||
/// return 0.CombineHashCode(field1).CombineHashCode(field2).
|
||||
/// CombineHashCode(field3);
|
||||
/// </summary>
|
||||
public static int CombineHashCode<T>(this int hashCode, T arg)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return 31 * hashCode + arg.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
|
||||
var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
|
||||
? Path.Combine(folder, filename)
|
||||
? Path.Combine(folder, filename).Replace('\\', '/')
|
||||
: folder + "-" + filename;
|
||||
|
||||
return filepath;
|
||||
|
||||
@@ -52,9 +52,20 @@ namespace Umbraco.Core.IO
|
||||
try
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
dir = dir.Substring(0, dir.Length - _shadowPath.Length - 1);
|
||||
if (Directory.EnumerateFileSystemEntries(dir).Any() == false)
|
||||
Directory.Delete(dir, true);
|
||||
|
||||
// shadowPath make be path/to/dir, remove each
|
||||
dir = dir.Replace("/", "\\");
|
||||
var min = IOHelper.MapPath("~/App_Data/TEMP/ShadowFs").Length;
|
||||
var pos = dir.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase);
|
||||
while (pos > min)
|
||||
{
|
||||
dir = dir.Substring(0, pos);
|
||||
if (Directory.EnumerateFileSystemEntries(dir).Any() == false)
|
||||
Directory.Delete(dir, true);
|
||||
else
|
||||
break;
|
||||
pos = dir.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Umbraco.Core.Models
|
||||
/// Used in content/media/member repositories in order to add this type of entity to the persisted collection to be saved
|
||||
/// in a single transaction during saving an entity
|
||||
/// </summary>
|
||||
internal class ContentXmlEntity<TContent> : IAggregateRoot
|
||||
internal class ContentXmlEntity<TContent> : IAggregateRoot // fixme kill?
|
||||
where TContent : IContentBase
|
||||
{
|
||||
private readonly Func<TContent, XElement> _xml;
|
||||
@@ -42,6 +42,7 @@ namespace Umbraco.Core.Models
|
||||
public Guid Key { get; set; }
|
||||
public DateTime CreateDate { get; set; }
|
||||
public DateTime UpdateDate { get; set; }
|
||||
public DateTime? DeletedDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Special case, always return false, this will cause the repositories managing
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace Umbraco.Core.Models.EntityBase
|
||||
{
|
||||
@@ -13,8 +10,8 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
[DebuggerDisplay("Id: {Id}")]
|
||||
public abstract class Entity : TracksChangesEntityBase, IEntity, IRememberBeingDirty, ICanBeDirty
|
||||
[DebuggerDisplay("Id: {" + nameof(Id) + "}")]
|
||||
public abstract class Entity : TracksChangesEntityBase, IEntity //, IRememberBeingDirty, ICanBeDirty
|
||||
{
|
||||
private bool _hasIdentity;
|
||||
private int _id;
|
||||
@@ -25,6 +22,7 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
|
||||
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class PropertySelectors
|
||||
{
|
||||
public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo<Entity, int>(x => x.Id);
|
||||
@@ -41,7 +39,7 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
[DataMember]
|
||||
public int Id
|
||||
{
|
||||
get { return _id; }
|
||||
get => _id;
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector);
|
||||
@@ -64,7 +62,7 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
_key = Guid.NewGuid();
|
||||
return _key;
|
||||
}
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); }
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,8 +71,8 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
[DataMember]
|
||||
public DateTime CreateDate
|
||||
{
|
||||
get { return _createDate; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); }
|
||||
get => _createDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,8 +85,8 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
[Obsolete("Anytime there's a cancellable method it needs to return an Attempt so we know the outcome instead of this hack, not all services have been updated to use this though yet.")]
|
||||
internal bool WasCancelled
|
||||
{
|
||||
get { return _wasCancelled; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _wasCancelled, Ps.Value.WasCancelledSelector); }
|
||||
get => _wasCancelled;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _wasCancelled, Ps.Value.WasCancelledSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,10 +95,13 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
[DataMember]
|
||||
public DateTime UpdateDate
|
||||
{
|
||||
get { return _updateDate; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); }
|
||||
get => _updateDate;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector);
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public DateTime? DeletedDate { get; set; }
|
||||
|
||||
internal virtual void ResetIdentity()
|
||||
{
|
||||
_hasIdentity = false;
|
||||
@@ -134,32 +135,19 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
[DataMember]
|
||||
public virtual bool HasIdentity
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hasIdentity;
|
||||
}
|
||||
protected set { SetPropertyValueAndDetectChanges(value, ref _hasIdentity, Ps.Value.HasIdentitySelector); }
|
||||
get => _hasIdentity;
|
||||
protected set => SetPropertyValueAndDetectChanges(value, ref _hasIdentity, Ps.Value.HasIdentitySelector);
|
||||
}
|
||||
|
||||
//TODO: Make this NOT virtual or even exist really!
|
||||
public virtual bool SameIdentityAs(IEntity other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return SameIdentityAs(other as Entity);
|
||||
return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other as Entity));
|
||||
}
|
||||
|
||||
public virtual bool Equals(Entity other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return SameIdentityAs(other);
|
||||
return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other));
|
||||
}
|
||||
|
||||
//TODO: Make this NOT virtual or even exist really!
|
||||
@@ -185,12 +173,7 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
|
||||
return SameIdentityAs(obj as IEntity);
|
||||
return obj != null && (ReferenceEquals(this, obj) || SameIdentityAs(obj as IEntity));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -208,7 +191,7 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
{
|
||||
//Memberwise clone on Entity will work since it doesn't have any deep elements
|
||||
// for any sub class this will work for standard properties as well that aren't complex object's themselves.
|
||||
var ignored = this.Key; // ensure that 'this' has a key, before cloning
|
||||
var unused = Key; // ensure that 'this' has a key, before cloning
|
||||
var clone = (Entity)MemberwiseClone();
|
||||
//ensure the clone has it's own dictionaries
|
||||
clone.ResetChangeTrackingCollections();
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
/// <summary>
|
||||
/// Marker interface for aggregate roots
|
||||
/// </summary>
|
||||
public interface IAggregateRoot : IEntity
|
||||
{
|
||||
|
||||
}
|
||||
public interface IAggregateRoot : IDeletableEntity
|
||||
{ }
|
||||
}
|
||||
11
src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs
Normal file
11
src/Umbraco.Core/Models/EntityBase/IDeletableEntity.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Models.EntityBase
|
||||
{
|
||||
public interface IDeletableEntity : IEntity
|
||||
{
|
||||
[DataMember]
|
||||
DateTime? DeletedDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,6 @@
|
||||
{
|
||||
public interface IPartialView : IFile
|
||||
{
|
||||
|
||||
PartialViewType ViewType { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Partial View file
|
||||
/// </summary>
|
||||
@@ -12,14 +10,21 @@ namespace Umbraco.Core.Models
|
||||
[DataContract(IsReference = true)]
|
||||
public class PartialView : File, IPartialView
|
||||
{
|
||||
[Obsolete("Use the ctor that explicitely sets the view type.")]
|
||||
public PartialView(string path)
|
||||
: this(path, null)
|
||||
: this(PartialViewType.PartialView, path, null)
|
||||
{ }
|
||||
|
||||
internal PartialView(string path, Func<File, string> getFileContent)
|
||||
public PartialView(PartialViewType viewType, string path)
|
||||
: this(viewType, path, null)
|
||||
{ }
|
||||
|
||||
internal PartialView(PartialViewType viewType, string path, Func<File, string> getFileContent)
|
||||
: base(path, getFileContent)
|
||||
{ }
|
||||
{
|
||||
ViewType = viewType;
|
||||
}
|
||||
|
||||
internal PartialViewType ViewType { get; set; }
|
||||
public PartialViewType ViewType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
internal enum PartialViewType : byte
|
||||
public enum PartialViewType : byte
|
||||
{
|
||||
Unknown = 0, // default
|
||||
PartialView = 1,
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
private readonly bool _keepOldest;
|
||||
|
||||
public OrderedHashSet(bool keepOldest = true)
|
||||
public OrderedHashSet(bool keepOldest = true)
|
||||
{
|
||||
_keepOldest = keepOldest;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NPoco;
|
||||
using System;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Alter;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Create;
|
||||
@@ -27,8 +28,15 @@ namespace Umbraco.Core.Persistence.Migrations
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public abstract void Up();
|
||||
public abstract void Down();
|
||||
public virtual void Up()
|
||||
{
|
||||
throw new NotSupportedException("This migration does not implement the \"up\" operation.");
|
||||
}
|
||||
|
||||
public virtual void Down()
|
||||
{
|
||||
throw new NotSupportedException("This migration does not implement the \"down\" operation.");
|
||||
}
|
||||
|
||||
public IAlterSyntaxBuilder Alter => new AlterSyntaxBuilder(Context);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NPoco;
|
||||
using System;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table;
|
||||
@@ -23,6 +24,15 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter
|
||||
return new AlterTableBuilder(_context, _supportedDatabaseTypes, expression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The problem with this is that only under particular circumstances is the expression added to the context
|
||||
/// so you wouldn't actually know if you are using it correctly or not and chances are you are not and therefore
|
||||
/// the statement won't even execute whereas using the IAlterTableSyntax to modify a column is guaranteed to add
|
||||
/// the expression to the context.
|
||||
/// </summary>
|
||||
/// <param name="columnName"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use the IAlterTableSyntax to modify a column instead, this will be removed in future versions")]
|
||||
public IAlterColumnSyntax Column(string columnName)
|
||||
{
|
||||
var expression = new AlterColumnExpression(_context, _supportedDatabaseTypes) {Column = {Name = columnName}};
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column;
|
||||
using System;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Column;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Table;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter
|
||||
{
|
||||
public interface IAlterSyntaxBuilder : IFluentSyntax
|
||||
{
|
||||
IAlterTableSyntax Table(string tableName);
|
||||
IAlterTableSyntax Table(string tableName);
|
||||
|
||||
/// <summary>
|
||||
/// The problem with this is that only under particular circumstances is the expression added to the context
|
||||
/// so you wouldn't actually know if you are using it correctly or not and chances are you are not and therefore
|
||||
/// the statement won't even execute whereas using the IAlterTableSyntax to modify a column is guaranteed to add
|
||||
/// the expression to the context.
|
||||
/// </summary>
|
||||
/// <param name="columnName"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use the IAlterTableSyntax to modify a column instead, this will be removed in future versions")]
|
||||
IAlterColumnSyntax Column(string columnName);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
{
|
||||
[Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)]
|
||||
[Migration("7.6.0", 3, Constants.System.UmbracoMigrationName)]
|
||||
public class AddIndexToCmsMemberLoginName : MigrationBase
|
||||
{
|
||||
public AddIndexToCmsMemberLoginName(IMigrationContext context)
|
||||
@@ -13,17 +13,40 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database);
|
||||
|
||||
//make sure it doesn't already exist
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false)
|
||||
Execute.Code(database =>
|
||||
{
|
||||
Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember")
|
||||
.OnColumn("LoginName")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.NonClustered();
|
||||
}
|
||||
//Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long
|
||||
//http://issues.umbraco.org/issue/U4-9758
|
||||
|
||||
var colLen = SqlSyntax is MySqlSyntaxProvider
|
||||
? database.ExecuteScalar<int?>("select max(LENGTH(LoginName)) from cmsMember")
|
||||
: database.ExecuteScalar<int?>("select max(datalength(LoginName)) from cmsMember");
|
||||
|
||||
if (colLen < 900 == false && colLen != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database);
|
||||
|
||||
//make sure it doesn't already exist
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false)
|
||||
{
|
||||
var local = Context.GetLocalMigration();
|
||||
|
||||
//we can apply the index
|
||||
local.Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember")
|
||||
.OnColumn("LoginName")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.NonClustered();
|
||||
|
||||
return local.GetSql();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
{
|
||||
[Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)]
|
||||
public class NormalizeTemplateGuids : MigrationBase
|
||||
{
|
||||
public NormalizeTemplateGuids(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
Execute.Code(UpdateTemplateGuids);
|
||||
}
|
||||
|
||||
private static string UpdateTemplateGuids(IUmbracoDatabase database)
|
||||
{
|
||||
// we need this migration because ppl running pre-7.6 on Cloud and Courier have templates in different
|
||||
// environments having different GUIDs (Courier does not sync template GUIDs) and we need to normalize
|
||||
// these GUIDs so templates with the same alias on different environments have the same GUID.
|
||||
// however, if already running a prerelease version of 7.6, we do NOT want to normalize the GUIDs as quite
|
||||
// probably, we are already running Deploy and the GUIDs are OK. assuming noone is running a prerelease
|
||||
// of 7.6 on Courier.
|
||||
// so... testing if we already have a 7.6.0 version installed. not pretty but...?
|
||||
//
|
||||
var version = database.FirstOrDefault<string>("SELECT version FROM umbracoMigration WHERE name=@name ORDER BY version DESC", new { name = Constants.System.UmbracoMigrationName });
|
||||
if (version != null && version.StartsWith("7.6.0")) return string.Empty;
|
||||
|
||||
var updates = database.Query<dynamic>(@"SELECT umbracoNode.id, cmsTemplate.alias FROM umbracoNode
|
||||
JOIN cmsTemplate ON umbracoNode.id=cmsTemplate.nodeId
|
||||
WHERE nodeObjectType = @guid", new { guid = Constants.ObjectTypes.TemplateTypeGuid})
|
||||
.Select(template => Tuple.Create((int) template.id, ("template____" + (string) template.alias).ToGuid()))
|
||||
.ToList();
|
||||
|
||||
foreach (var update in updates)
|
||||
database.Execute("UPDATE umbracoNode set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 });
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
throw new WontImplementException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
{
|
||||
[Migration("7.6.0", 2, Constants.System.UmbracoMigrationName)]
|
||||
public class ReduceLoginNameColumnsSize : MigrationBase
|
||||
{
|
||||
public ReduceLoginNameColumnsSize(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
//Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long
|
||||
//http://issues.umbraco.org/issue/U4-9758
|
||||
|
||||
Execute.Code(database =>
|
||||
{
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database);
|
||||
|
||||
var colLen = (SqlSyntax is MySqlSyntaxProvider)
|
||||
? database.ExecuteScalar<int?>("select max(LENGTH(LoginName)) from cmsMember")
|
||||
: database.ExecuteScalar<int?>("select max(datalength(LoginName)) from cmsMember");
|
||||
|
||||
if (colLen < 900 == false) return null;
|
||||
|
||||
var local = Context.GetLocalMigration();
|
||||
|
||||
//if it exists we need to drop it first
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")))
|
||||
{
|
||||
local.Delete.Index("IX_cmsMember_LoginName").OnTable("cmsMember");
|
||||
}
|
||||
|
||||
//we can apply the col length change
|
||||
local.Alter.Table("cmsMember")
|
||||
.AlterColumn("LoginName")
|
||||
.AsString(225)
|
||||
.NotNullable();
|
||||
|
||||
return local.GetSql();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe
|
||||
}).ToArray();
|
||||
|
||||
//must be non-nullable
|
||||
Alter.Column("uniqueID").OnTable("umbracoNode").AsGuid().NotNullable();
|
||||
Alter.Table("umbracoNode").AlterColumn("uniqueID").AsGuid().NotNullable();
|
||||
|
||||
//make sure it already exists
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodeUniqueID")))
|
||||
|
||||
@@ -285,6 +285,8 @@ AND umbracoNode.id <> @id",
|
||||
|
||||
//Delete (base) node data
|
||||
Database.Delete<NodeDto>("WHERE uniqueID = @Id", new { Id = entity.Key });
|
||||
|
||||
entity.DeletedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -551,6 +553,8 @@ AND umbracoNode.id <> @id",
|
||||
protected override void PersistDeletedItem(PreValueEntity entity)
|
||||
{
|
||||
Database.Execute("DELETE FROM cmsDataTypePreValues WHERE id=@Id", new { Id = entity.Id });
|
||||
|
||||
entity.DeletedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
protected override void PersistNewItem(PreValueEntity entity)
|
||||
|
||||
@@ -195,6 +195,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.ItemKey));
|
||||
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.Key));
|
||||
|
||||
entity.DeletedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
private void RecursiveDelete(Guid parentId)
|
||||
|
||||
@@ -157,6 +157,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
// delete
|
||||
Database.Delete(nodeDto);
|
||||
|
||||
entity.DeletedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
protected override void PersistNewItem(EntityContainer entity)
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
Database.Execute(delete, new { Id = GetEntityId(entity) });
|
||||
}
|
||||
entity.DeletedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var updated = FileSystem.GetLastModified(path).UtcDateTime;
|
||||
//var content = GetFileContent(path);
|
||||
|
||||
var view = new PartialView(path, file => GetFileContent(file.OriginalPath))
|
||||
var view = new PartialView(ViewType, path, file => GetFileContent(file.OriginalPath))
|
||||
{
|
||||
//id can be the hash
|
||||
Id = path.GetHashCode(),
|
||||
@@ -38,8 +38,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Content = content,
|
||||
CreateDate = created,
|
||||
UpdateDate = updated,
|
||||
VirtualPath = FileSystem.GetUrl(id),
|
||||
ViewType = ViewType
|
||||
VirtualPath = FileSystem.GetUrl(id)
|
||||
};
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
|
||||
@@ -120,6 +120,11 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
Database.Update(dto);
|
||||
|
||||
foreach (var removedRule in entity.RemovedRules)
|
||||
{
|
||||
Database.Delete<AccessRuleDto>("WHERE id=@Id", new { Id = removedRule });
|
||||
}
|
||||
|
||||
foreach (var rule in entity.Rules)
|
||||
{
|
||||
if (rule.HasIdentity)
|
||||
@@ -145,10 +150,6 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
rule.Id = rule.Key.GetHashCode();
|
||||
}
|
||||
}
|
||||
foreach (var removedRule in entity.RemovedRules)
|
||||
{
|
||||
Database.Delete<AccessRuleDto>("WHERE id=@Id", new {Id = removedRule});
|
||||
}
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
@@ -318,6 +318,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var masterpageName = string.Concat(entity.Alias, ".master");
|
||||
_masterpagesFileSystem.DeleteFile(masterpageName);
|
||||
}
|
||||
|
||||
entity.DeletedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
@@ -19,6 +21,47 @@ namespace Umbraco.Core.Plugins
|
||||
|
||||
private static readonly Assembly[] EmptyAssemblies = new Assembly[0];
|
||||
|
||||
/// <summary>
|
||||
/// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too.
|
||||
/// If it cannot be done, null is returned.
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
// fixme wtf is this and do we need it in v8?
|
||||
internal static IList CreateGenericEnumerableFromObject(object obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var genericTypeDef = type.GetGenericTypeDefinition();
|
||||
|
||||
if (genericTypeDef == typeof(IEnumerable<>)
|
||||
|| genericTypeDef == typeof(ICollection<>)
|
||||
|| genericTypeDef == typeof(Collection<>)
|
||||
|| genericTypeDef == typeof(IList<>)
|
||||
|| genericTypeDef == typeof(List<>)
|
||||
//this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types
|
||||
|| obj is IEnumerable)
|
||||
{
|
||||
//if it is a IEnumerable<>, IList<T> or ICollection<> we'll use a List<>
|
||||
var genericType = typeof(List<>).MakeGenericType(type.GetGenericArguments());
|
||||
//pass in obj to fill the list
|
||||
return (IList)Activator.CreateInstance(genericType, obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
{
|
||||
//if its an array, we'll use a List<>
|
||||
var genericType = typeof(List<>).MakeGenericType(type.GetElementType());
|
||||
//pass in obj to fill the list
|
||||
return (IList)Activator.CreateInstance(genericType, obj);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the method is actually overriding a base method
|
||||
/// </summary>
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
|
||||
public override object ConvertInterToObject(PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview)
|
||||
{
|
||||
return source.ToString();
|
||||
return source?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +433,7 @@ namespace Umbraco.Core.Scoping
|
||||
}
|
||||
finally
|
||||
{
|
||||
// removes the ambient context (ambient scope already gone)
|
||||
_scopeProvider.SetAmbient(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Scoping
|
||||
{
|
||||
@@ -9,11 +10,17 @@ namespace Umbraco.Core.Scoping
|
||||
public class ScopeContext : IInstanceIdentifiable
|
||||
{
|
||||
private Dictionary<string, IEnlistedObject> _enlisted;
|
||||
private bool _exiting;
|
||||
|
||||
public void ScopeExit(bool completed)
|
||||
{
|
||||
if (_enlisted == null)
|
||||
return;
|
||||
|
||||
_exiting = true;
|
||||
|
||||
List<Exception> exceptions = null;
|
||||
foreach (var enlisted in Enlisted.Values)
|
||||
foreach (var enlisted in _enlisted.Values.OrderBy(x => x.Priority))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -26,6 +33,7 @@ namespace Umbraco.Core.Scoping
|
||||
exceptions.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions != null)
|
||||
throw new AggregateException("Exceptions were thrown by listed actions.", exceptions);
|
||||
}
|
||||
@@ -38,73 +46,71 @@ namespace Umbraco.Core.Scoping
|
||||
private interface IEnlistedObject
|
||||
{
|
||||
void Execute(bool completed);
|
||||
int Priority { get; }
|
||||
}
|
||||
|
||||
private class EnlistedObject<T> : IEnlistedObject
|
||||
{
|
||||
private readonly Action<bool, T> _action;
|
||||
|
||||
public EnlistedObject(T item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public EnlistedObject(T item, Action<bool, T> action)
|
||||
public EnlistedObject(T item, Action<bool, T> action, int priority)
|
||||
{
|
||||
Item = item;
|
||||
Priority = priority;
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public T Item { get; }
|
||||
|
||||
public int Priority { get; private set; }
|
||||
|
||||
public void Execute(bool completed)
|
||||
{
|
||||
_action(completed, Item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
// todo: replace with optional parameters when we can break things
|
||||
public T Enlist<T>(string key, Func<T> creator)
|
||||
{
|
||||
IEnlistedObject enlisted;
|
||||
if (Enlisted.TryGetValue(key, out enlisted))
|
||||
{
|
||||
var enlistedAs = enlisted as EnlistedObject<T>;
|
||||
if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key.");
|
||||
return enlistedAs.Item;
|
||||
}
|
||||
var enlistedOfT = new EnlistedObject<T>(creator());
|
||||
Enlisted[key] = enlistedOfT;
|
||||
return enlistedOfT.Item;
|
||||
return Enlist(key, creator, null, 100);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
// todo: replace with optional parameters when we can break things
|
||||
public T Enlist<T>(string key, Func<T> creator, Action<bool, T> action)
|
||||
{
|
||||
IEnlistedObject enlisted;
|
||||
if (Enlisted.TryGetValue(key, out enlisted))
|
||||
{
|
||||
var enlistedAs = enlisted as EnlistedObject<T>;
|
||||
if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key.");
|
||||
return enlistedAs.Item;
|
||||
}
|
||||
var enlistedOfT = new EnlistedObject<T>(creator(), action);
|
||||
Enlisted[key] = enlistedOfT;
|
||||
return enlistedOfT.Item;
|
||||
return Enlist(key, creator, action, 100);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
// todo: replace with optional parameters when we can break things
|
||||
public void Enlist(string key, Action<bool> action)
|
||||
{
|
||||
Enlist<object>(key, null, (completed, item) => action(completed), 100);
|
||||
}
|
||||
|
||||
public void Enlist(string key, Action<bool> action, int priority)
|
||||
{
|
||||
Enlist<object>(key, null, (completed, item) => action(completed), priority);
|
||||
}
|
||||
|
||||
public T Enlist<T>(string key, Func<T> creator, Action<bool, T> action, int priority)
|
||||
{
|
||||
if (_exiting)
|
||||
throw new InvalidOperationException("Cannot enlist now, context is exiting.");
|
||||
|
||||
var enlistedObjects = _enlisted ?? (_enlisted = new Dictionary<string, IEnlistedObject>());
|
||||
|
||||
IEnlistedObject enlisted;
|
||||
if (Enlisted.TryGetValue(key, out enlisted))
|
||||
if (enlistedObjects.TryGetValue(key, out enlisted))
|
||||
{
|
||||
var enlistedAs = enlisted as EnlistedObject<object>;
|
||||
if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key.");
|
||||
return;
|
||||
var enlistedAs = enlisted as EnlistedObject<T>;
|
||||
if (enlistedAs == null) throw new InvalidOperationException("An item with the key already exists, but with a different type.");
|
||||
if (enlistedAs.Priority != priority) throw new InvalidOperationException("An item with the key already exits, but with a different priority.");
|
||||
return enlistedAs.Item;
|
||||
}
|
||||
var enlistedOfT = new EnlistedObject<object>(null, (completed, item) => action(completed));
|
||||
var enlistedOfT = new EnlistedObject<T>(creator == null ? default(T) : creator(), action, priority);
|
||||
Enlisted[key] = enlistedOfT;
|
||||
return enlistedOfT.Item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ namespace Umbraco.Core.Security
|
||||
|
||||
//track the last login date
|
||||
user.LastLoginDateUtc = DateTime.UtcNow;
|
||||
user.AccessFailedCount = 0;
|
||||
await UserManager.UpdateAsync(user);
|
||||
|
||||
_logger.WriteCore(TraceEventType.Information, 0,
|
||||
|
||||
@@ -126,35 +126,11 @@ namespace Umbraco.Core.Services
|
||||
|
||||
private static Guid GetNodeObjectTypeGuid(UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
switch (umbracoObjectType)
|
||||
{
|
||||
case UmbracoObjectTypes.Document:
|
||||
return Constants.ObjectTypes.DocumentGuid;
|
||||
case UmbracoObjectTypes.MemberType:
|
||||
return Constants.ObjectTypes.MemberTypeGuid;
|
||||
case UmbracoObjectTypes.Media:
|
||||
return Constants.ObjectTypes.MediaGuid;
|
||||
case UmbracoObjectTypes.Template:
|
||||
return Constants.ObjectTypes.TemplateTypeGuid;
|
||||
case UmbracoObjectTypes.MediaType:
|
||||
return Constants.ObjectTypes.MediaTypeGuid;
|
||||
case UmbracoObjectTypes.DocumentType:
|
||||
return Constants.ObjectTypes.DocumentTypeGuid;
|
||||
case UmbracoObjectTypes.Member:
|
||||
return Constants.ObjectTypes.MemberGuid;
|
||||
case UmbracoObjectTypes.DataType:
|
||||
return Constants.ObjectTypes.DataTypeGuid;
|
||||
case UmbracoObjectTypes.MemberGroup:
|
||||
return Constants.ObjectTypes.MemberGroupGuid;
|
||||
case UmbracoObjectTypes.RecycleBin:
|
||||
case UmbracoObjectTypes.Stylesheet:
|
||||
case UmbracoObjectTypes.ContentItem:
|
||||
case UmbracoObjectTypes.ContentItemType:
|
||||
case UmbracoObjectTypes.ROOT:
|
||||
case UmbracoObjectTypes.Unknown:
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported object type (" + umbracoObjectType + ").");
|
||||
}
|
||||
var guid = umbracoObjectType.GetGuid();
|
||||
if (guid == Guid.Empty)
|
||||
throw new NotSupportedException("Unsupported object type (" + umbracoObjectType + ").");
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -24,7 +25,9 @@ namespace Umbraco.Core.Services
|
||||
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repo = uow.CreateRepository<IExternalLoginRepository>();
|
||||
return repo.GetByQuery(repo.QueryT.Where(x => x.UserId == userId));
|
||||
return repo
|
||||
.GetByQuery(repo.QueryT.Where(x => x.UserId == userId))
|
||||
.ToList(); // ToList is important here, must evaluate within uow!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +42,9 @@ namespace Umbraco.Core.Services
|
||||
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repo = uow.CreateRepository<IExternalLoginRepository>();
|
||||
return repo.GetByQuery(repo.QueryT
|
||||
.Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider));
|
||||
return repo
|
||||
.GetByQuery(repo.QueryT.Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider))
|
||||
.ToList(); // ToList is important here, must evaluate within uow!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
@@ -186,5 +187,14 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="raiseEvents">Optional parameter indicating whether or not to raise events</param>
|
||||
/// <returns><see cref="XElement"/> containing the xml representation of the IMacro object</returns>
|
||||
XElement Export(IMacro macro, bool raiseEvents = true);
|
||||
|
||||
/// <summary>
|
||||
/// This will fetch an Umbraco package file from the package repository and return the relative file path to the downloaded package file
|
||||
/// </summary>
|
||||
/// <param name="packageId"></param>
|
||||
/// <param name="umbracoVersion"></param>
|
||||
/// <param name="userId">The current user id performing the operation</param>
|
||||
/// <returns></returns>
|
||||
string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using System.Web.UI.WebControls;
|
||||
@@ -9,8 +11,10 @@ using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.DI;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -49,7 +53,6 @@ namespace Umbraco.Core.Services
|
||||
private IPackageInstallation _packageInstallation;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
|
||||
public PackagingService(
|
||||
ILogger logger,
|
||||
IContentService contentService,
|
||||
@@ -1458,6 +1461,65 @@ namespace Umbraco.Core.Services
|
||||
#region Package Manifest
|
||||
#endregion
|
||||
|
||||
#region Package Files
|
||||
|
||||
/// <summary>
|
||||
/// This will fetch an Umbraco package file from the package repository and return the relative file path to the downloaded package file
|
||||
/// </summary>
|
||||
/// <param name="packageId"></param>
|
||||
/// <param name="umbracoVersion"></param>
|
||||
/// /// <param name="userId">The current user id performing the operation</param>
|
||||
/// <returns></returns>
|
||||
public string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId)
|
||||
{
|
||||
var packageRepo = UmbracoConfig.For.UmbracoSettings().PackageRepositories.GetDefault();
|
||||
|
||||
using (var httpClient = new HttpClient())
|
||||
using (var uow = _uowProvider.CreateUnitOfWork())
|
||||
{
|
||||
//includeHidden = true because we don't care if it's hidden we want to get the file regardless
|
||||
var url = $"{packageRepo.RestApiUrl}/{packageId}?version={umbracoVersion.ToString(3)}&includeHidden=true&asFile=true";
|
||||
byte[] bytes;
|
||||
try
|
||||
{
|
||||
bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
throw new ConnectionException("An error occuring downloading the package from " + url, ex);
|
||||
}
|
||||
|
||||
//successfull
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
var packagePath = IOHelper.MapPath(SystemDirectories.Packages);
|
||||
|
||||
// Check for package directory
|
||||
if (Directory.Exists(packagePath) == false)
|
||||
Directory.CreateDirectory(packagePath);
|
||||
|
||||
var packageFilePath = Path.Combine(packagePath, packageId + ".umb");
|
||||
|
||||
using (var fs1 = new FileStream(packageFilePath, FileMode.Create))
|
||||
{
|
||||
fs1.Write(bytes, 0, bytes.Length);
|
||||
return "packages\\" + packageId + ".umb";
|
||||
}
|
||||
}
|
||||
|
||||
Audit(uow, AuditType.PackagerInstall, $"Package {packageId} fetched from {packageRepo.Id}", userId, -1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Audit(IUnitOfWork uow, AuditType type, string message, int userId, int objectId)
|
||||
{
|
||||
var auditRepo = uow.CreateRepository<IAuditRepository>();
|
||||
auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Templates
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Core
|
||||
/// <param name="entityType">The entity type part of the udi.</param>
|
||||
/// <param name="id">The string id part of the udi.</param>
|
||||
public StringUdi(string entityType, string id)
|
||||
: base(entityType, "umb://" + entityType + "/" + id)
|
||||
: base(entityType, "umb://" + entityType + "/" + Uri.EscapeUriString(id))
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Core
|
||||
public StringUdi(Uri uriValue)
|
||||
: base(uriValue)
|
||||
{
|
||||
Id = uriValue.AbsolutePath.TrimStart('/');
|
||||
Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart('/'));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
@@ -17,12 +17,12 @@ namespace Umbraco.Core.Sync
|
||||
private readonly ServerRole _serverRole;
|
||||
private readonly string _umbracoApplicationUrl;
|
||||
|
||||
public ConfigServerRegistrar(IUmbracoSettingsSection settings)
|
||||
: this(settings.DistributedCall)
|
||||
public ConfigServerRegistrar(IUmbracoSettingsSection settings, ILogger logger)
|
||||
: this(settings.DistributedCall, logger)
|
||||
{ }
|
||||
|
||||
// for tests
|
||||
internal ConfigServerRegistrar(IDistributedCallSection settings)
|
||||
internal ConfigServerRegistrar(IDistributedCallSection settings, ILogger logger)
|
||||
{
|
||||
if (settings.Enabled == false)
|
||||
{
|
||||
@@ -42,6 +42,7 @@ namespace Umbraco.Core.Sync
|
||||
if (serversA.Length == 0)
|
||||
{
|
||||
_serverRole = ServerRole.Unknown; // config error, actually
|
||||
logger.Debug<ConfigServerRegistrar>("Server Role Unknown: DistributedCalls are enabled but no servers are listed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -50,17 +51,23 @@ namespace Umbraco.Core.Sync
|
||||
var serverName = master.ServerName;
|
||||
|
||||
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
|
||||
{
|
||||
_serverRole = ServerRole.Unknown; // config error, actually
|
||||
logger.Debug<ConfigServerRegistrar>("Server Role Unknown: Server Name or AppId missing from Server configuration in DistributedCalls settings.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_serverRole = IsCurrentServer(appId, serverName)
|
||||
? ServerRole.Master
|
||||
: ServerRole.Slave;
|
||||
}
|
||||
}
|
||||
|
||||
var currentServer = serversA.FirstOrDefault(x => IsCurrentServer(x.AppId, x.ServerName));
|
||||
if (currentServer != null)
|
||||
{
|
||||
// match, use the configured url
|
||||
// ReSharper disable once UseStringInterpolation
|
||||
_umbracoApplicationUrl = string.Format("{0}://{1}:{2}/{3}",
|
||||
currentServer.ForceProtocol.IsNullOrWhiteSpace() ? "http" : currentServer.ForceProtocol,
|
||||
currentServer.ServerAddress,
|
||||
@@ -76,19 +83,10 @@ namespace Umbraco.Core.Sync
|
||||
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName));
|
||||
}
|
||||
|
||||
public IEnumerable<IServerAddress> Registrations
|
||||
{
|
||||
get { return _addresses; }
|
||||
}
|
||||
public IEnumerable<IServerAddress> Registrations => _addresses;
|
||||
|
||||
public ServerRole GetCurrentServerRole()
|
||||
{
|
||||
return _serverRole;
|
||||
}
|
||||
public ServerRole GetCurrentServerRole() => _serverRole;
|
||||
|
||||
public string GetCurrentServerUmbracoApplicationUrl()
|
||||
{
|
||||
return _umbracoApplicationUrl;
|
||||
}
|
||||
public string GetCurrentServerUmbracoApplicationUrl() => _umbracoApplicationUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,8 @@ namespace Umbraco.Core
|
||||
public override string ToString()
|
||||
{
|
||||
// UriValue is created in the ctor and is never null
|
||||
return UriValue.ToString();
|
||||
// use AbsoluteUri here and not ToString else it's not encoded!
|
||||
return UriValue.AbsoluteUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -161,7 +162,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
if (udiType == UdiType.StringUdi)
|
||||
{
|
||||
udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, path);
|
||||
udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path));
|
||||
return true;
|
||||
}
|
||||
if (tryParse) return false;
|
||||
|
||||
@@ -198,7 +198,13 @@ namespace Umbraco.Core
|
||||
public static StringUdi GetUdi(this IPartialView entity)
|
||||
{
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
return new StringUdi(Constants.UdiEntityType.PartialView, entity.Path.TrimStart('/')).EnsureClosed();
|
||||
|
||||
// we should throw on Unknown but for the time being, assume it means PartialView
|
||||
var entityType = entity.ViewType == PartialViewType.PartialViewMacro
|
||||
? Constants.UdiEntityType.PartialViewMacro
|
||||
: Constants.UdiEntityType.PartialView;
|
||||
|
||||
return new StringUdi(entityType, entity.Path.TrimStart('/')).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user