Merge branch 'netcore/dev' of https://github.com/umbraco/Umbraco-CMS into feature/netcore-model-unit-tests-settings
This commit is contained in:
@@ -37,4 +37,3 @@ Besides "Our", we all support each other also via Twitter: [Umbraco HQ](https://
|
||||
## Contributing
|
||||
|
||||
Umbraco is contribution-focused and community-driven. If you want to contribute back to the Umbraco source code, please check out our [guide to contributing](CONTRIBUTING.md).
|
||||
|
||||
|
||||
@@ -176,3 +176,4 @@ build/temp/
|
||||
/src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/lib/*
|
||||
/src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/views/*
|
||||
/src/Umbraco.Web.UI.NetCore/wwwroot/App_Data/TEMP/*
|
||||
/src/Umbraco.Web.UI.NetCore/App_Data/Logs/*
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
namespace Umbraco.Web.Actions
|
||||
{
|
||||
internal class ActionCollectionBuilder : LazyCollectionBuilderBase<ActionCollectionBuilder, ActionCollection, IAction>
|
||||
public class ActionCollectionBuilder : LazyCollectionBuilderBase<ActionCollectionBuilder, ActionCollection, IAction>
|
||||
{
|
||||
protected override ActionCollectionBuilder This => this;
|
||||
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a fast <see cref="IAppCache"/> on top of HttpContext.Items.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If no current HttpContext items can be found (no current HttpContext,
|
||||
/// or no Items...) then this cache acts as a pass-through and does not cache
|
||||
/// anything.</para>
|
||||
/// </remarks>
|
||||
public class GenericDictionaryRequestAppCache : FastDictionaryAppCacheBase, IRequestCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpRequestAppCache"/> class with a context, for unit tests!
|
||||
/// </summary>
|
||||
public GenericDictionaryRequestAppCache(Func<IDictionary<object, object>> requestItems) : base()
|
||||
{
|
||||
ContextItems = requestItems;
|
||||
}
|
||||
|
||||
private Func<IDictionary<object, object>> ContextItems { get; }
|
||||
|
||||
public bool IsAvailable => TryGetContextItems(out _);
|
||||
|
||||
private bool TryGetContextItems(out IDictionary<object, object> items)
|
||||
{
|
||||
items = ContextItems?.Invoke();
|
||||
return items != null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object Get(string key, Func<object> factory)
|
||||
{
|
||||
//no place to cache so just return the callback result
|
||||
if (!TryGetContextItems(out var items)) return factory();
|
||||
|
||||
key = GetCacheKey(key);
|
||||
|
||||
Lazy<object> result;
|
||||
|
||||
try
|
||||
{
|
||||
EnterWriteLock();
|
||||
result = items[key] as Lazy<object>; // null if key not found
|
||||
|
||||
// cannot create value within the lock, so if result.IsValueCreated is false, just
|
||||
// do nothing here - means that if creation throws, a race condition could cause
|
||||
// more than one thread to reach the return statement below and throw - accepted.
|
||||
|
||||
if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
{
|
||||
result = SafeLazy.GetSafeLazy(factory);
|
||||
items[key] = result;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ExitWriteLock();
|
||||
}
|
||||
|
||||
// using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
|
||||
// exceptions (but try again and again) and silently eat them - however at
|
||||
// some point we have to report them - so need to re-throw here
|
||||
|
||||
// this does not throw anymore
|
||||
//return result.Value;
|
||||
|
||||
var value = result.Value; // will not throw (safe lazy)
|
||||
if (value is SafeLazy.ExceptionHolder eh) eh.Exception.Throw(); // throw once!
|
||||
return value;
|
||||
}
|
||||
|
||||
public bool Set(string key, object value)
|
||||
{
|
||||
//no place to cache so just return the callback result
|
||||
if (!TryGetContextItems(out var items)) return false;
|
||||
key = GetCacheKey(key);
|
||||
try
|
||||
{
|
||||
|
||||
EnterWriteLock();
|
||||
items[key] = SafeLazy.GetSafeLazy(() => value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ExitWriteLock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
//no place to cache so just return the callback result
|
||||
if (!TryGetContextItems(out var items)) return false;
|
||||
key = GetCacheKey(key);
|
||||
try
|
||||
{
|
||||
|
||||
EnterWriteLock();
|
||||
items.Remove(key);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ExitWriteLock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Entries
|
||||
|
||||
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
|
||||
{
|
||||
const string prefix = CacheItemPrefix + "-";
|
||||
|
||||
if (!TryGetContextItems(out var items)) return Enumerable.Empty<DictionaryEntry>();
|
||||
|
||||
return items.Cast<DictionaryEntry>()
|
||||
.Where(x => x.Key is string s && s.StartsWith(prefix));
|
||||
}
|
||||
|
||||
protected override void RemoveEntry(string key)
|
||||
{
|
||||
if (!TryGetContextItems(out var items)) return;
|
||||
|
||||
items.Remove(key);
|
||||
}
|
||||
|
||||
protected override object GetEntry(string key)
|
||||
{
|
||||
return !TryGetContextItems(out var items) ? null : items[key];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lock
|
||||
|
||||
private const string ContextItemsLockKey = "Umbraco.Core.Cache.HttpRequestCache::LockEntered";
|
||||
|
||||
protected override void EnterReadLock() => EnterWriteLock();
|
||||
|
||||
protected override void EnterWriteLock()
|
||||
{
|
||||
if (!TryGetContextItems(out var items)) return;
|
||||
|
||||
// note: cannot keep 'entered' as a class variable here,
|
||||
// since there is one per request - so storing it within
|
||||
// ContextItems - which is locked, so this should be safe
|
||||
|
||||
var entered = false;
|
||||
Monitor.Enter(items, ref entered);
|
||||
items[ContextItemsLockKey] = entered;
|
||||
}
|
||||
|
||||
protected override void ExitReadLock() => ExitWriteLock();
|
||||
|
||||
protected override void ExitWriteLock()
|
||||
{
|
||||
if (!TryGetContextItems(out var items)) return;
|
||||
|
||||
var entered = (bool?)items[ContextItemsLockKey] ?? false;
|
||||
if (entered)
|
||||
Monitor.Exit(items);
|
||||
items.Remove(ContextItemsLockKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
if (!TryGetContextItems(out var items))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <returns></returns>
|
||||
internal static ActionCollectionBuilder Actions(this Composition composition)
|
||||
public static ActionCollectionBuilder Actions(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<ActionCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
@@ -45,7 +45,7 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <returns></returns>
|
||||
internal static EditorValidatorCollectionBuilder EditorValidators(this Composition composition)
|
||||
public static EditorValidatorCollectionBuilder EditorValidators(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<EditorValidatorCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
@@ -81,7 +81,7 @@ namespace Umbraco.Core
|
||||
/// <param name="composition">The composition.</param>
|
||||
public static SectionCollectionBuilder Sections(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<SectionCollectionBuilder>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the components collection builder.
|
||||
/// </summary>
|
||||
|
||||
@@ -31,5 +31,20 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public static void RegisterUnique<TService>(this Composition composition, TService instance)
|
||||
=> composition.RegisterUnique(typeof(TService), instance);
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Registers a unique service with an implementation type.
|
||||
/// </summary>
|
||||
public static void RegisterMultipleUnique<TService1, TService2, TImplementing>(this Composition composition)
|
||||
where TImplementing : class, TService1, TService2
|
||||
where TService1 : class
|
||||
where TService2 : class
|
||||
{
|
||||
composition.RegisterUnique<TImplementing>();
|
||||
composition.RegisterUnique<TService1>(factory => factory.GetInstance<TImplementing>());
|
||||
composition.RegisterUnique<TService2>(factory => factory.GetInstance<TImplementing>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class SqlTemplates
|
||||
{
|
||||
public static class VersionableRepository
|
||||
{
|
||||
public const string GetVersionIds = "Umbraco.Core.VersionableRepository.GetVersionIds";
|
||||
public const string GetVersion = "Umbraco.Core.VersionableRepository.GetVersion";
|
||||
public const string GetVersions = "Umbraco.Core.VersionableRepository.GetVersions";
|
||||
public const string EnsureUniqueNodeName = "Umbraco.Core.VersionableRepository.EnsureUniqueNodeName";
|
||||
public const string GetSortOrder = "Umbraco.Core.VersionableRepository.GetSortOrder";
|
||||
public const string GetParentNode = "Umbraco.Core.VersionableRepository.GetParentNode";
|
||||
public const string GetReservedId = "Umbraco.Core.VersionableRepository.GetReservedId";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Web.ContentApps
|
||||
{
|
||||
internal class ContentEditorContentAppFactory : IContentAppFactory
|
||||
public class ContentEditorContentAppFactory : IContentAppFactory
|
||||
{
|
||||
// see note on ContentApp
|
||||
internal const int Weight = -100;
|
||||
|
||||
@@ -10,7 +10,7 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.ContentApps
|
||||
{
|
||||
internal class ListViewContentAppFactory : IContentAppFactory
|
||||
public class ListViewContentAppFactory : IContentAppFactory
|
||||
{
|
||||
// see note on ContentApp
|
||||
private const int Weight = -666;
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
public static class ContentExtensions
|
||||
{
|
||||
|
||||
#region XML methods
|
||||
|
||||
/// <summary>
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ using Umbraco.Core.Events;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
internal class DefaultEventMessagesFactory : IEventMessagesFactory
|
||||
public class DefaultEventMessagesFactory : IEventMessagesFactory
|
||||
{
|
||||
private readonly IEventMessagesAccessor _eventMessagesAccessor;
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace Umbraco.Core.Hosting
|
||||
{
|
||||
string SiteName { get; }
|
||||
string ApplicationId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Will return the physical path to the root of the application
|
||||
/// </summary>
|
||||
string ApplicationPhysicalPath { get; }
|
||||
|
||||
string LocalTempPath { get; }
|
||||
@@ -27,10 +31,22 @@ namespace Umbraco.Core.Hosting
|
||||
bool IsHosted { get; }
|
||||
|
||||
Version IISVersion { get; }
|
||||
|
||||
// TODO: Should we change this name to MapPathWebRoot ? and also have a new MapPathContentRoot ?
|
||||
|
||||
/// <summary>
|
||||
/// Maps a virtual path to a physical path to the application's web root
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however
|
||||
/// in netcore the web root is /www therefore this will Map to a physical path within www.
|
||||
/// </remarks>
|
||||
string MapPath(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a virtual path to the application's web root
|
||||
/// Converts a virtual path to an absolute URL path based on the application's web root
|
||||
/// </summary>
|
||||
/// <param name="virtualPath">The virtual path. Must start with either ~/ or / else an exception is thrown.</param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -3,7 +3,7 @@ using Umbraco.Core.Events;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
internal class HybridEventMessagesAccessor : HybridAccessorBase<EventMessages>, IEventMessagesAccessor
|
||||
public class HybridEventMessagesAccessor : HybridAccessorBase<EventMessages>, IEventMessagesAccessor
|
||||
{
|
||||
protected override string ItemKey => "Umbraco.Core.Events.HybridEventMessagesAccessor";
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
|
||||
public interface ILoggingConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The physical path where logs are stored
|
||||
/// </summary>
|
||||
string LogDirectory { get; }
|
||||
string LogConfigurationFile { get; }
|
||||
string UserLogConfigurationFile { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,12 @@
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Defines the profiling service.
|
||||
/// </summary>
|
||||
public interface IProfiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the profiling results.
|
||||
/// </summary>
|
||||
/// <returns>The profiling results.</returns>
|
||||
/// <remarks>Generally used for HTML rendering.</remarks>
|
||||
string Render();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IDisposable"/> that will time the code between its creation and disposal.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to render a profiler in a web page
|
||||
/// </summary>
|
||||
public interface IProfilerHtml
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the profiling results.
|
||||
/// </summary>
|
||||
/// <returns>The profiling results.</returns>
|
||||
/// <remarks>Generally used for HTML rendering.</remarks>
|
||||
string Render();
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,6 @@ namespace Umbraco.Core.Logging
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Render()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDisposable Step(string name)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
public class LoggingConfiguration : ILoggingConfiguration
|
||||
{
|
||||
public LoggingConfiguration(string logDirectory, string logConfigurationFile, string userLogConfigurationFile)
|
||||
{
|
||||
LogDirectory = logDirectory ?? throw new ArgumentNullException(nameof(logDirectory));
|
||||
LogConfigurationFile = logConfigurationFile ?? throw new ArgumentNullException(nameof(logConfigurationFile));
|
||||
UserLogConfigurationFile = userLogConfigurationFile ?? throw new ArgumentNullException(nameof(userLogConfigurationFile));
|
||||
}
|
||||
|
||||
public string LogDirectory { get; }
|
||||
|
||||
public string LogConfigurationFile { get; }
|
||||
|
||||
public string UserLogConfigurationFile { get; }
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,6 @@ namespace Umbraco.Core.Logging
|
||||
{
|
||||
private readonly VoidDisposable _disposable = new VoidDisposable();
|
||||
|
||||
public string Render()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public IDisposable Step(string name)
|
||||
{
|
||||
return _disposable;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public class ContentDataIntegrityReport
|
||||
{
|
||||
public ContentDataIntegrityReport(IReadOnlyDictionary<int, ContentDataIntegrityReportEntry> detectedIssues)
|
||||
{
|
||||
DetectedIssues = detectedIssues;
|
||||
}
|
||||
|
||||
public bool Ok => DetectedIssues.Count == 0 || DetectedIssues.Count == DetectedIssues.Values.Count(x => x.Fixed);
|
||||
|
||||
public IReadOnlyDictionary<int, ContentDataIntegrityReportEntry> DetectedIssues { get; }
|
||||
|
||||
public IReadOnlyDictionary<int, ContentDataIntegrityReportEntry> FixedIssues
|
||||
=> DetectedIssues.Where(x => x.Value.Fixed).ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
public enum IssueType
|
||||
{
|
||||
/// <summary>
|
||||
/// The item's level and path are inconsistent with it's parent's path and level
|
||||
/// </summary>
|
||||
InvalidPathAndLevelByParentId,
|
||||
|
||||
/// <summary>
|
||||
/// The item's path doesn't contain all required parts
|
||||
/// </summary>
|
||||
InvalidPathEmpty,
|
||||
|
||||
/// <summary>
|
||||
/// The item's path parts are inconsistent with it's level value
|
||||
/// </summary>
|
||||
InvalidPathLevelMismatch,
|
||||
|
||||
/// <summary>
|
||||
/// The item's path does not end with it's own ID
|
||||
/// </summary>
|
||||
InvalidPathById,
|
||||
|
||||
/// <summary>
|
||||
/// The item's path does not have it's parent Id as the 2nd last entry
|
||||
/// </summary>
|
||||
InvalidPathByParentId,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public class ContentDataIntegrityReportEntry
|
||||
{
|
||||
public ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType issueType)
|
||||
{
|
||||
IssueType = issueType;
|
||||
}
|
||||
|
||||
public ContentDataIntegrityReport.IssueType IssueType { get; }
|
||||
public bool Fixed { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public class ContentDataIntegrityReportOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Set to true to try to automatically resolve data integrity issues
|
||||
/// </summary>
|
||||
public bool FixIssues { get; set; }
|
||||
|
||||
// TODO: We could define all sorts of options for the data integrity check like what to check for, what to fix, etc...
|
||||
// things like Tag data consistency, etc...
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -13,20 +14,25 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
private string _name;
|
||||
private string _alias;
|
||||
private bool _isBidrectional;
|
||||
private bool _isBidirectional;
|
||||
private Guid? _parentObjectType;
|
||||
private Guid? _childObjectType;
|
||||
|
||||
public RelationType(string alias, string name)
|
||||
: this(name, alias, false, null, null)
|
||||
: this(name: name, alias: alias, false, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name));
|
||||
if (alias == null) throw new ArgumentNullException(nameof(alias));
|
||||
if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias));
|
||||
|
||||
_name = name;
|
||||
_alias = alias;
|
||||
_isBidrectional = isBidrectional;
|
||||
_isBidirectional = isBidrectional;
|
||||
_parentObjectType = parentObjectType;
|
||||
_childObjectType = childObjectType;
|
||||
}
|
||||
@@ -57,8 +63,8 @@ namespace Umbraco.Core.Models
|
||||
[DataMember]
|
||||
public bool IsBidirectional
|
||||
{
|
||||
get => _isBidrectional;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isBidrectional, nameof(IsBidirectional));
|
||||
get => _isBidirectional;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isBidirectional, nameof(IsBidirectional));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Net
|
||||
{
|
||||
public class NullSessionIdResolver : ISessionIdResolver
|
||||
{
|
||||
public string SessionId => null;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
namespace Umbraco.Core.Services
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Placeholder for sharing logic between the content, media (and member) services
|
||||
/// TODO: Start sharing the logic!
|
||||
/// </summary>
|
||||
public interface IContentServiceBase : IService
|
||||
{ }
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks/fixes the data integrity of node paths/levels stored in the database
|
||||
/// </summary>
|
||||
ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using Umbraco.Web.Sections;
|
||||
|
||||
namespace Umbraco.Web.Services
|
||||
{
|
||||
internal class SectionService : ISectionService
|
||||
public class SectionService : ISectionService
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly SectionCollection _sectionCollection;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Web.Services
|
||||
/// <summary>
|
||||
/// Implements <see cref="ITreeService"/>.
|
||||
/// </summary>
|
||||
internal class TreeService : ITreeService
|
||||
public class TreeService : ITreeService
|
||||
{
|
||||
private readonly TreeCollection _treeCollection;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Umbraco.Web
|
||||
{
|
||||
public interface ISessionManager
|
||||
{
|
||||
object GetSessionValue(string sessionName);
|
||||
void SetSessionValue(string sessionName, object value);
|
||||
string GetSessionValue(string sessionName);
|
||||
void SetSessionValue(string sessionName, string value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,17 @@ namespace Umbraco.Core.Composing
|
||||
/// <param name="builder"></param>
|
||||
/// <returns></returns>
|
||||
public static IHostBuilder UseUmbraco(this IHostBuilder builder)
|
||||
=> builder.UseUmbraco(new UmbracoServiceProviderFactory());
|
||||
{
|
||||
return builder
|
||||
.UseUmbraco(new UmbracoServiceProviderFactory());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a custom service provider factory to use Umbraco's container
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="umbracoServiceProviderFactory"></param>
|
||||
/// <returns></returns>
|
||||
public static IHostBuilder UseUmbraco(this IHostBuilder builder, UmbracoServiceProviderFactory umbracoServiceProviderFactory)
|
||||
=> builder.UseServiceProviderFactory(umbracoServiceProviderFactory);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Web.Media.EmbedProviders;
|
||||
using Umbraco.Web.Search;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -84,7 +86,20 @@ namespace Umbraco.Core
|
||||
public static ManifestFilterCollectionBuilder ManifestFilters(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<ManifestFilterCollectionBuilder>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backoffice OEmbed Providers collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
public static EmbedProvidersCollectionBuilder OEmbedProviders(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<EmbedProvidersCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the back office searchable tree collection builder
|
||||
/// </summary>
|
||||
/// <param name="composition"></param>
|
||||
/// <returns></returns>
|
||||
public static SearchableTreeCollectionBuilder SearchableTrees(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<SearchableTreeCollectionBuilder>();
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -98,7 +113,7 @@ namespace Umbraco.Core
|
||||
public static void SetCultureDictionaryFactory<T>(this Composition composition)
|
||||
where T : ICultureDictionaryFactory
|
||||
{
|
||||
composition.RegisterUnique<ICultureDictionaryFactory, T>();
|
||||
composition.RegisterUnique<ICultureDictionaryFactory, T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,11 +4,9 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Composing;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
@@ -16,6 +14,20 @@ namespace Umbraco.Core
|
||||
{
|
||||
public static class ContentExtensions
|
||||
{
|
||||
|
||||
internal static bool IsMoving(this IContentBase entity)
|
||||
{
|
||||
// Check if this entity is being moved as a descendant as part of a bulk moving operations.
|
||||
// When this occurs, only Path + Level + UpdateDate are being changed. In this case we can bypass a lot of the below
|
||||
// operations which will make this whole operation go much faster. When moving we don't need to create
|
||||
// new versions, etc... because we cannot roll this operation back anyways.
|
||||
var isMoving = entity.IsPropertyDirty(nameof(entity.Path))
|
||||
&& entity.IsPropertyDirty(nameof(entity.Level))
|
||||
&& entity.IsPropertyDirty(nameof(entity.UpdateDate));
|
||||
|
||||
return isMoving;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes characters that are not valid XML characters from all entity properties
|
||||
/// of type string. See: http://stackoverflow.com/a/961504/5018
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Diagnostics
|
||||
@@ -100,7 +101,7 @@ namespace Umbraco.Core.Diagnostics
|
||||
return bRet;
|
||||
}
|
||||
|
||||
public static bool Dump(IMarchal marchal, IIOHelper ioHelper, Option options = Option.WithFullMemory, bool withException = false)
|
||||
public static bool Dump(IMarchal marchal, IHostingEnvironment hostingEnvironment, Option options = Option.WithFullMemory, bool withException = false)
|
||||
{
|
||||
lock (LockO)
|
||||
{
|
||||
@@ -110,7 +111,7 @@ namespace Umbraco.Core.Diagnostics
|
||||
// filter everywhere in our code = not!
|
||||
var stacktrace = withException ? Environment.StackTrace : string.Empty;
|
||||
|
||||
var filepath = ioHelper.MapPath("~/App_Data/MiniDump");
|
||||
var filepath = Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data/MiniDump");
|
||||
if (Directory.Exists(filepath) == false)
|
||||
Directory.CreateDirectory(filepath);
|
||||
|
||||
@@ -122,11 +123,11 @@ namespace Umbraco.Core.Diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
public static bool OkToDump(IIOHelper ioHelper)
|
||||
public static bool OkToDump(IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
lock (LockO)
|
||||
{
|
||||
var filepath = ioHelper.MapPath("~/App_Data/MiniDump");
|
||||
var filepath = Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data/MiniDump");
|
||||
if (Directory.Exists(filepath) == false) return true;
|
||||
var count = Directory.GetFiles(filepath, "*.dmp").Length;
|
||||
return count < 8;
|
||||
|
||||
@@ -16,14 +16,14 @@ namespace Umbraco.Core.Logging
|
||||
// but it only has a pre-release NuGet package. So, we've got to use Serilog's code, which
|
||||
// means we cannot get rid of Serilog entirely. We may want to revisit this at some point.
|
||||
|
||||
// TODO: Do we still need this, is there a non-pre release package shipped?
|
||||
|
||||
private static readonly Lazy<global::Serilog.ILogger> MinimalLogger = new Lazy<global::Serilog.ILogger>(() => new LoggerConfiguration().CreateLogger());
|
||||
|
||||
public string Render(string messageTemplate, params object[] args)
|
||||
{
|
||||
// by default, unless initialized otherwise, Log.Logger is SilentLogger which cannot bind message
|
||||
// templates. Log.Logger is set to a true Logger when initializing Umbraco's logger, but in case
|
||||
// that has not been done already - use a temp minimal logger (eg for tests).
|
||||
var logger = Log.Logger as global::Serilog.Core.Logger ?? MinimalLogger.Value;
|
||||
// resolve a minimal logger instance which is used to bind message templates
|
||||
var logger = MinimalLogger.Value;
|
||||
|
||||
var bound = logger.BindMessageTemplate(messageTemplate, args, out var parsedTemplate, out var boundProperties);
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestIdEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
|
||||
/// </summary>
|
||||
internal class HttpRequestIdEnricher : ILogEventEnricher
|
||||
public class HttpRequestIdEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly Func<IRequestCache> _requestCacheGetter;
|
||||
private readonly IRequestCache _requestCache;
|
||||
|
||||
public HttpRequestIdEnricher(Func<IRequestCache> requestCacheGetter)
|
||||
public HttpRequestIdEnricher(IRequestCache requestCache)
|
||||
{
|
||||
_requestCacheGetter = requestCacheGetter;
|
||||
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -34,11 +34,8 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
{
|
||||
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
|
||||
|
||||
var requestCache = _requestCacheGetter();
|
||||
if(requestCache is null) return;
|
||||
|
||||
Guid requestId;
|
||||
if (!LogHttpRequest.TryGetCurrentHttpRequestId(out requestId, requestCache))
|
||||
if (!LogHttpRequest.TryGetCurrentHttpRequestId(out requestId, _requestCache))
|
||||
return;
|
||||
|
||||
var requestIdProperty = new LogEventProperty(HttpRequestIdPropertyName, new ScalarValue(requestId));
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpRequestNumberEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
|
||||
/// </summary>
|
||||
internal class HttpRequestNumberEnricher : ILogEventEnricher
|
||||
public class HttpRequestNumberEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly Func<IRequestCache> _requestCacheGetter;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private static int _lastRequestNumber;
|
||||
private static readonly string _requestNumberItemName = typeof(HttpRequestNumberEnricher).Name + "+RequestNumber";
|
||||
|
||||
@@ -25,9 +25,9 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
private const string _httpRequestNumberPropertyName = "HttpRequestNumber";
|
||||
|
||||
|
||||
public HttpRequestNumberEnricher(Func<IRequestCache> requestCacheGetter)
|
||||
public HttpRequestNumberEnricher(IRequestCache requestCache)
|
||||
{
|
||||
_requestCacheGetter = requestCacheGetter;
|
||||
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,10 +39,7 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
{
|
||||
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
|
||||
|
||||
var requestCache = _requestCacheGetter();
|
||||
if (requestCache is null) return;
|
||||
|
||||
var requestNumber = requestCache.Get(_requestNumberItemName,
|
||||
var requestNumber = _requestCache.Get(_requestNumberItemName,
|
||||
() => Interlocked.Increment(ref _lastRequestNumber));
|
||||
|
||||
var requestNumberProperty = new LogEventProperty(_httpRequestNumberPropertyName, new ScalarValue(requestNumber));
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Logging.Serilog.Enrichers
|
||||
/// Original source - https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/HttpSessionIdEnricher.cs
|
||||
/// Nupkg: 'Serilog.Web.Classic' contains handlers & extra bits we do not want
|
||||
/// </summary>
|
||||
internal class HttpSessionIdEnricher : ILogEventEnricher
|
||||
public class HttpSessionIdEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly ISessionIdResolver _sessionIdResolver;
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Diagnostics;
|
||||
using Umbraco.Core.Hosting;
|
||||
|
||||
namespace Umbraco.Infrastructure.Logging.Serilog.Enrichers
|
||||
{
|
||||
/// <summary>
|
||||
/// Enriches the log if there are ThreadAbort exceptions and will automatically create a minidump if it can
|
||||
/// </summary>
|
||||
public class ThreadAbortExceptionEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly ICoreDebugSettings _coreDebugSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IMarchal _marchal;
|
||||
|
||||
public ThreadAbortExceptionEnricher(ICoreDebugSettings coreDebugSettings, IHostingEnvironment hostingEnvironment, IMarchal marchal)
|
||||
{
|
||||
_coreDebugSettings = coreDebugSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_marchal = marchal;
|
||||
}
|
||||
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
switch (logEvent.Level)
|
||||
{
|
||||
case LogEventLevel.Error:
|
||||
case LogEventLevel.Fatal:
|
||||
DumpThreadAborts(logEvent, propertyFactory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpThreadAborts(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if (!IsTimeoutThreadAbortException(logEvent.Exception)) return;
|
||||
|
||||
var message = "The thread has been aborted, because the request has timed out.";
|
||||
|
||||
// dump if configured, or if stacktrace contains Monitor.ReliableEnter
|
||||
var dump = _coreDebugSettings.DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(logEvent.Exception);
|
||||
|
||||
// dump if it is ok to dump (might have a cap on number of dump...)
|
||||
dump &= MiniDump.OkToDump(_hostingEnvironment);
|
||||
|
||||
if (!dump)
|
||||
{
|
||||
message += ". No minidump was created.";
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
var dumped = MiniDump.Dump(_marchal, _hostingEnvironment, withException: true);
|
||||
message += dumped
|
||||
? ". A minidump was created in App_Data/MiniDump."
|
||||
: ". Failed to create a minidump.";
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
message = "Failed to create a minidump. " + ex;
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTimeoutThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
if (abort.ExceptionState == null) return false;
|
||||
|
||||
var stateType = abort.ExceptionState.GetType();
|
||||
if (stateType.FullName != "System.Web.HttpApplication+CancelModuleException") return false;
|
||||
|
||||
var timeoutField = stateType.GetField("_timeout", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (timeoutField == null) return false;
|
||||
|
||||
return (bool)timeoutField.GetValue(abort.ExceptionState);
|
||||
}
|
||||
|
||||
private static bool IsMonitorEnterThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
|
||||
var stacktrace = abort.StackTrace;
|
||||
return stacktrace.Contains("System.Threading.Monitor.ReliableEnter");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Serilog;
|
||||
using Serilog.Configuration;
|
||||
@@ -6,11 +7,8 @@ using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting;
|
||||
using Serilog.Formatting.Compact;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Core.Logging.Serilog
|
||||
{
|
||||
@@ -24,27 +22,30 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// It is highly recommended that you keep/use this default in your own logging config customizations
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
public static LoggerConfiguration MinimalConfiguration(this LoggerConfiguration logConfig, IHostingEnvironment hostingEnvironment, ISessionIdResolver sessionIdResolver, Func<IRequestCache> requestCacheGetter)
|
||||
/// <param name="IHostingEnvironment"></param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
public static LoggerConfiguration MinimalConfiguration(
|
||||
this LoggerConfiguration logConfig,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
global::Serilog.Debugging.SelfLog.Enable(msg => System.Diagnostics.Debug.WriteLine(msg));
|
||||
|
||||
//Set this environment variable - so that it can be used in external config file
|
||||
//add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" />
|
||||
Environment.SetEnvironmentVariable("BASEDIR", AppDomain.CurrentDomain.BaseDirectory, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("MACHINENAME", Environment.MachineName, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("UMBLOGDIR", loggingConfiguration.LogDirectory, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("BASEDIR", hostingEnvironment.ApplicationPhysicalPath, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("MACHINENAME", Environment.MachineName, EnvironmentVariableTarget.Process);
|
||||
|
||||
logConfig.MinimumLevel.Verbose() //Set to highest level of logging (as any sinks may want to restrict it to Errors only)
|
||||
.Enrich.WithProcessId()
|
||||
.Enrich.WithProcessName()
|
||||
.Enrich.WithThreadId()
|
||||
.Enrich.WithProperty(AppDomainId, AppDomain.CurrentDomain.Id)
|
||||
.Enrich.WithProperty(AppDomainId, AppDomain.CurrentDomain.Id)
|
||||
.Enrich.WithProperty("AppDomainAppId", hostingEnvironment.ApplicationId.ReplaceNonAlphanumericChars(string.Empty))
|
||||
.Enrich.WithProperty("MachineName", Environment.MachineName)
|
||||
.Enrich.With<Log4NetLevelMapperEnricher>()
|
||||
.Enrich.With(new HttpSessionIdEnricher(sessionIdResolver))
|
||||
.Enrich.With(new HttpRequestNumberEnricher(requestCacheGetter))
|
||||
.Enrich.With(new HttpRequestIdEnricher(requestCacheGetter));
|
||||
.Enrich.FromLogContext(); // allows us to dynamically enrich
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
@@ -53,13 +54,14 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// Outputs a .txt format log at /App_Data/Logs/
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
/// <param name="minimumLevel">The log level you wish the JSON file to collect - default is Verbose (highest)</param>
|
||||
/// <param name="retainedFileCount">The number of days to keep log files. Default is set to null which means all logs are kept</param>
|
||||
public static LoggerConfiguration OutputDefaultTextFile(this LoggerConfiguration logConfig, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
public static LoggerConfiguration OutputDefaultTextFile(this LoggerConfiguration logConfig, ILoggingConfiguration loggingConfiguration, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
{
|
||||
//Main .txt logfile - in similar format to older Log4Net output
|
||||
//Ends with ..txt as Date is inserted before file extension substring
|
||||
logConfig.WriteTo.File($@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\UmbracoTraceLog.{Environment.MachineName}..txt",
|
||||
logConfig.WriteTo.File(Path.Combine(loggingConfiguration.LogDirectory, $@"UmbracoTraceLog.{Environment.MachineName}..txt"),
|
||||
shared: true,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
restrictedToMinimumLevel: minimumLevel,
|
||||
@@ -99,7 +101,8 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
rollingInterval,
|
||||
rollOnFileSizeLimit,
|
||||
retainedFileCountLimit,
|
||||
encoding),
|
||||
encoding,
|
||||
null),
|
||||
sinkMapCountLimit:0)
|
||||
);
|
||||
}
|
||||
@@ -109,13 +112,14 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// Outputs a CLEF format JSON log at /App_Data/Logs/
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
/// <param name="minimumLevel">The log level you wish the JSON file to collect - default is Verbose (highest)</param>
|
||||
/// <param name="retainedFileCount">The number of days to keep log files. Default is set to null which means all logs are kept</param>
|
||||
public static LoggerConfiguration OutputDefaultJsonFile(this LoggerConfiguration logConfig, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
public static LoggerConfiguration OutputDefaultJsonFile(this LoggerConfiguration logConfig, ILoggingConfiguration loggingConfiguration, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
{
|
||||
//.clef format (Compact log event format, that can be imported into local SEQ & will make searching/filtering logs easier)
|
||||
//Ends with ..txt as Date is inserted before file extension substring
|
||||
logConfig.WriteTo.File(new CompactJsonFormatter(), $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\UmbracoTraceLog.{Environment.MachineName}..json",
|
||||
logConfig.WriteTo.File(new CompactJsonFormatter(), Path.Combine(loggingConfiguration.LogDirectory, $@"UmbracoTraceLog.{Environment.MachineName}..json"),
|
||||
shared: true,
|
||||
rollingInterval: RollingInterval.Day, //Create a new JSON file every day
|
||||
retainedFileCountLimit: retainedFileCount, //Setting to null means we keep all files - default is 31 days
|
||||
@@ -129,10 +133,11 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// That allows the main logging pipeline to be configured
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
public static LoggerConfiguration ReadFromConfigFile(this LoggerConfiguration logConfig)
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
public static LoggerConfiguration ReadFromConfigFile(this LoggerConfiguration logConfig, ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
//Read from main serilog.config file
|
||||
logConfig.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + @"\config\serilog.config");
|
||||
logConfig.ReadFrom.AppSettings(filePath: loggingConfiguration.LogConfigurationFile);
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
@@ -142,13 +147,15 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// That allows a separate logging pipeline to be configured that will not affect the main Umbraco log
|
||||
/// </summary>
|
||||
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
|
||||
public static LoggerConfiguration ReadFromUserConfigFile(this LoggerConfiguration logConfig)
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
public static LoggerConfiguration ReadFromUserConfigFile(this LoggerConfiguration logConfig, ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
//A nested logger - where any user configured sinks via config can not effect the main 'umbraco' logger above
|
||||
logConfig.WriteTo.Logger(cfg =>
|
||||
cfg.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + @"\config\serilog.user.config"));
|
||||
cfg.ReadFrom.AppSettings(filePath: loggingConfiguration.UserLogConfigurationFile));
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Infrastructure.Logging.Serilog.Enrichers;
|
||||
|
||||
namespace Umbraco.Infrastructure.Logging.Serilog
|
||||
{
|
||||
public class SerilogComposer : ICoreComposer
|
||||
{
|
||||
public void Compose(Composition composition)
|
||||
{
|
||||
composition.RegisterUnique<ThreadAbortExceptionEnricher>();
|
||||
composition.RegisterUnique<HttpSessionIdEnricher>();
|
||||
composition.RegisterUnique<HttpRequestNumberEnricher>();
|
||||
composition.RegisterUnique<HttpRequestIdEnricher>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Diagnostics;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Net;
|
||||
|
||||
namespace Umbraco.Core.Logging.Serilog
|
||||
{
|
||||
|
||||
///<summary>
|
||||
/// Implements <see cref="ILogger"/> on top of Serilog.
|
||||
///</summary>
|
||||
public class SerilogLogger : ILogger, IDisposable
|
||||
{
|
||||
private readonly ICoreDebugSettings _coreDebugSettings;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly IMarchal _marchal;
|
||||
public global::Serilog.ILogger SerilogLog { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the <see cref="SerilogLogger"/> class with a configuration file.
|
||||
/// </summary>
|
||||
/// <param name="logConfigFile"></param>
|
||||
public SerilogLogger(ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal, FileInfo logConfigFile)
|
||||
public SerilogLogger(FileInfo logConfigFile)
|
||||
{
|
||||
_coreDebugSettings = coreDebugSettings;
|
||||
_ioHelper = ioHelper;
|
||||
_marchal = marchal;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + logConfigFile)
|
||||
SerilogLog = new LoggerConfiguration()
|
||||
.ReadFrom.AppSettings(filePath: logConfigFile.FullName)
|
||||
.CreateLogger();
|
||||
}
|
||||
|
||||
public SerilogLogger(ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal, LoggerConfiguration logConfig)
|
||||
public SerilogLogger(LoggerConfiguration logConfig)
|
||||
{
|
||||
_coreDebugSettings = coreDebugSettings;
|
||||
_ioHelper = ioHelper;
|
||||
_marchal = marchal;
|
||||
|
||||
//Configure Serilog static global logger with config passed in
|
||||
Log.Logger = logConfig.CreateLogger();
|
||||
SerilogLog = logConfig.CreateLogger();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a logger with some pre-defined configuration and remainder from config file
|
||||
/// </summary>
|
||||
/// <remarks>Used by UmbracoApplicationBase to get its logger.</remarks>
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(IHostingEnvironment hostingEnvironment, ISessionIdResolver sessionIdResolver, Func<IRequestCache> requestCacheGetter, ICoreDebugSettings coreDebugSettings, IIOHelper ioHelper, IMarchal marchal)
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(IHostingEnvironment hostingEnvironment, ILoggingConfiguration loggingConfiguration)
|
||||
{
|
||||
var loggerConfig = new LoggerConfiguration();
|
||||
loggerConfig
|
||||
.MinimalConfiguration(hostingEnvironment, sessionIdResolver, requestCacheGetter)
|
||||
.ReadFromConfigFile()
|
||||
.ReadFromUserConfigFile();
|
||||
.MinimalConfiguration(hostingEnvironment, loggingConfiguration)
|
||||
.ReadFromConfigFile(loggingConfiguration)
|
||||
.ReadFromUserConfigFile(loggingConfiguration);
|
||||
|
||||
return new SerilogLogger(coreDebugSettings, ioHelper, marchal, loggerConfig);
|
||||
return new SerilogLogger(loggerConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a contextualized logger.
|
||||
/// </summary>
|
||||
private global::Serilog.ILogger LoggerFor(Type reporting)
|
||||
=> Log.Logger.ForContext(reporting);
|
||||
=> SerilogLog.ForContext(reporting);
|
||||
|
||||
/// <summary>
|
||||
/// Maps Umbraco's log level to Serilog's.
|
||||
@@ -99,8 +83,7 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, Exception exception, string message)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref message);
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Fatal(exception, message);
|
||||
}
|
||||
|
||||
@@ -108,8 +91,7 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
public void Fatal(Type reporting, Exception exception)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
var message = "Exception.";
|
||||
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref message);
|
||||
var message = "Exception.";
|
||||
logger.Fatal(exception, message);
|
||||
}
|
||||
|
||||
@@ -128,16 +110,14 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
/// <inheritdoc/>
|
||||
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
DumpThreadAborts(logger, LogEventLevel.Fatal, exception, ref messageTemplate);
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Fatal(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Error(Type reporting, Exception exception, string message)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref message);
|
||||
var logger = LoggerFor(reporting);
|
||||
logger.Error(exception, message);
|
||||
}
|
||||
|
||||
@@ -146,7 +126,6 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
var message = "Exception";
|
||||
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref message);
|
||||
logger.Error(exception, message);
|
||||
}
|
||||
|
||||
@@ -166,67 +145,9 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
{
|
||||
var logger = LoggerFor(reporting);
|
||||
DumpThreadAborts(logger, LogEventLevel.Error, exception, ref messageTemplate);
|
||||
logger.Error(exception, messageTemplate, propertyValues);
|
||||
}
|
||||
|
||||
private void DumpThreadAborts(global::Serilog.ILogger logger, LogEventLevel level, Exception exception, ref string messageTemplate)
|
||||
{
|
||||
var dump = false;
|
||||
|
||||
if (IsTimeoutThreadAbortException(exception))
|
||||
{
|
||||
messageTemplate += "\r\nThe thread has been aborted, because the request has timed out.";
|
||||
|
||||
// dump if configured, or if stacktrace contains Monitor.ReliableEnter
|
||||
dump = _coreDebugSettings.DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception);
|
||||
|
||||
// dump if it is ok to dump (might have a cap on number of dump...)
|
||||
dump &= MiniDump.OkToDump(_ioHelper);
|
||||
}
|
||||
|
||||
if (dump)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dumped = MiniDump.Dump(_marchal, _ioHelper, withException: true);
|
||||
messageTemplate += dumped
|
||||
? "\r\nA minidump was created in App_Data/MiniDump"
|
||||
: "\r\nFailed to create a minidump";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
messageTemplate += "\r\nFailed to create a minidump";
|
||||
|
||||
//Log a new entry (as opposed to appending to same log entry)
|
||||
logger.Write(level, ex, "Failed to create a minidump ({ExType}: {ExMessage})",
|
||||
new object[]{ ex.GetType().FullName, ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsMonitorEnterThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
|
||||
var stacktrace = abort.StackTrace;
|
||||
return stacktrace.Contains("System.Threading.Monitor.ReliableEnter");
|
||||
}
|
||||
|
||||
private static bool IsTimeoutThreadAbortException(Exception exception)
|
||||
{
|
||||
if (!(exception is ThreadAbortException abort)) return false;
|
||||
if (abort.ExceptionState == null) return false;
|
||||
|
||||
var stateType = abort.ExceptionState.GetType();
|
||||
if (stateType.FullName != "System.Web.HttpApplication+CancelModuleException") return false;
|
||||
|
||||
var timeoutField = stateType.GetField("_timeout", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (timeoutField == null) return false;
|
||||
|
||||
return (bool) timeoutField.GetValue(abort.ExceptionState);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Warn(Type reporting, string message)
|
||||
{
|
||||
@@ -289,7 +210,7 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
SerilogLog.DisposeIfDisposable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public interface ILogViewerConfig
|
||||
{
|
||||
IReadOnlyList<SavedLogSearch> GetSavedSearches();
|
||||
IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query);
|
||||
IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public void Compose(Composition composition)
|
||||
{
|
||||
composition.SetLogViewer(factory => new JsonLogViewer(composition.Logger, factory.GetInstance<IIOHelper>()));
|
||||
composition.RegisterUnique<ILogViewerConfig, LogViewerConfig>();
|
||||
composition.SetLogViewer<SerilogJsonLogViewer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public class LogViewerConfig : ILogViewerConfig
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private const string _pathToSearches = "~/Config/logviewer.searches.config.js";
|
||||
private readonly FileInfo _searchesConfig;
|
||||
|
||||
public LogViewerConfig(IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
var trimmedPath = _pathToSearches.TrimStart('~', '/').Replace('/', Path.DirectorySeparatorChar);
|
||||
var absolutePath = Path.Combine(_hostingEnvironment.ApplicationPhysicalPath, trimmedPath);
|
||||
_searchesConfig = new FileInfo(absolutePath);
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch> GetSavedSearches()
|
||||
{
|
||||
//Our default implementation
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists();
|
||||
|
||||
var rawJson = System.IO.File.ReadAllText(_searchesConfig.FullName);
|
||||
return JsonConvert.DeserializeObject<SavedLogSearch[]>(rawJson);
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Add the new item to the bottom of the list
|
||||
searches.Add(new SavedLogSearch { Name = name, Query = query });
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists();
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfig.FullName, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
//As opposed to push a new item into the array
|
||||
return searches;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Removes the search
|
||||
searches.RemoveAll(s => s.Name.Equals(name) && s.Query.Equals(query));
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfig.FullName, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
return searches;
|
||||
}
|
||||
|
||||
private void EnsureFileExists()
|
||||
{
|
||||
if (_searchesConfig.Exists) return;
|
||||
using (var writer = _searchesConfig.CreateText())
|
||||
{
|
||||
writer.Write("[]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -5,22 +5,25 @@ using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Compact.Reader;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
internal class JsonLogViewer : LogViewerSourceBase
|
||||
internal class SerilogJsonLogViewer : SerilogLogViewerSourceBase
|
||||
{
|
||||
private readonly string _logsPath;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public JsonLogViewer(ILogger logger, IIOHelper ioHelper, string logsPath = "", string searchPath = "") : base(ioHelper, searchPath)
|
||||
public SerilogJsonLogViewer(
|
||||
ILogger logger,
|
||||
ILogViewerConfig logViewerConfig,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
global::Serilog.ILogger serilogLog)
|
||||
: base(logViewerConfig, serilogLog)
|
||||
{
|
||||
if (string.IsNullOrEmpty(logsPath))
|
||||
logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\";
|
||||
|
||||
_logsPath = logsPath;
|
||||
_logger = logger;
|
||||
_logsPath = loggingConfiguration.LogDirectory;
|
||||
}
|
||||
|
||||
private const int FileSizeCap = 100;
|
||||
@@ -62,9 +65,6 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
var logs = new List<LogEvent>();
|
||||
|
||||
//Log Directory
|
||||
var logDirectory = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\";
|
||||
|
||||
var count = 0;
|
||||
|
||||
//foreach full day in the range - see if we can find one or more filenames that end with
|
||||
@@ -74,7 +74,7 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
//Filename ending to search for (As could be multiple)
|
||||
var filesToFind = GetSearchPattern(day);
|
||||
|
||||
var filesForCurrentDay = Directory.GetFiles(logDirectory, filesToFind);
|
||||
var filesForCurrentDay = Directory.GetFiles(_logsPath, filesToFind);
|
||||
|
||||
//Foreach file we find - open it
|
||||
foreach (var filePath in filesForCurrentDay)
|
||||
@@ -130,7 +130,7 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
// As we are reading/streaming one line at a time in the JSON file
|
||||
// Thus we can not report the line number, as it will always be 1
|
||||
_logger.Error<JsonLogViewer>(ex, "Unable to parse a line in the JSON log file");
|
||||
_logger.Error<SerilogJsonLogViewer>(ex, "Unable to parse a line in the JSON log file");
|
||||
|
||||
evt = null;
|
||||
return true;
|
||||
+13
-73
@@ -1,31 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
public abstract class LogViewerSourceBase : ILogViewer
|
||||
|
||||
public abstract class SerilogLogViewerSourceBase : ILogViewer
|
||||
{
|
||||
private readonly string _searchesConfigPath;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly ILogViewerConfig _logViewerConfig;
|
||||
private readonly global::Serilog.ILogger _serilogLog;
|
||||
|
||||
protected LogViewerSourceBase(IIOHelper ioHelper, string pathToSearches = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(pathToSearches))
|
||||
// ReSharper disable once StringLiteralTypo
|
||||
pathToSearches = ioHelper.MapPath("~/Config/logviewer.searches.config.js");
|
||||
|
||||
_searchesConfigPath = pathToSearches;
|
||||
_ioHelper = ioHelper;
|
||||
protected SerilogLogViewerSourceBase(ILogViewerConfig logViewerConfig, global::Serilog.ILogger serilogLog)
|
||||
{
|
||||
_logViewerConfig = logViewerConfig;
|
||||
_serilogLog = serilogLog;
|
||||
}
|
||||
|
||||
public abstract bool CanHandleLargeLogs { get; }
|
||||
@@ -38,55 +29,13 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
public abstract bool CheckCanOpenLogs(LogTimePeriod logTimePeriod);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> GetSavedSearches()
|
||||
{
|
||||
//Our default implementation
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists(_searchesConfigPath, "[]", _ioHelper);
|
||||
|
||||
var rawJson = System.IO.File.ReadAllText(_searchesConfigPath);
|
||||
return JsonConvert.DeserializeObject<SavedLogSearch[]>(rawJson);
|
||||
}
|
||||
=> _logViewerConfig.GetSavedSearches();
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> AddSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Add the new item to the bottom of the list
|
||||
searches.Add(new SavedLogSearch { Name = name, Query = query });
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//If file does not exist - lets create it with an empty array
|
||||
EnsureFileExists(_searchesConfigPath, "[]", _ioHelper);
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfigPath, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
//As opposed to push a new item into the array
|
||||
return searches;
|
||||
}
|
||||
=> _logViewerConfig.AddSavedSearch(name, query);
|
||||
|
||||
public virtual IReadOnlyList<SavedLogSearch> DeleteSavedSearch(string name, string query)
|
||||
{
|
||||
//Get the existing items
|
||||
var searches = GetSavedSearches().ToList();
|
||||
|
||||
//Removes the search
|
||||
searches.RemoveAll(s => s.Name.Equals(name) && s.Query.Equals(query));
|
||||
|
||||
//Serialize to JSON string
|
||||
var rawJson = JsonConvert.SerializeObject(searches, Formatting.Indented);
|
||||
|
||||
//Write it back down to file
|
||||
System.IO.File.WriteAllText(_searchesConfigPath, rawJson);
|
||||
|
||||
//Return the updated object - so we can instantly reset the entire array from the API response
|
||||
return searches;
|
||||
}
|
||||
=> _logViewerConfig.DeleteSavedSearch(name, query);
|
||||
|
||||
public int GetNumberOfErrors(LogTimePeriod logTimePeriod)
|
||||
{
|
||||
@@ -101,7 +50,7 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
/// <returns></returns>
|
||||
public string GetLogLevel()
|
||||
{
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(Log.Logger.IsEnabled)?.Min() ?? null;
|
||||
var logLevel = Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Where(_serilogLog.IsEnabled)?.Min() ?? null;
|
||||
return logLevel?.ToString() ?? "";
|
||||
}
|
||||
|
||||
@@ -182,15 +131,6 @@ namespace Umbraco.Core.Logging.Viewer
|
||||
};
|
||||
}
|
||||
|
||||
private static void EnsureFileExists(string path, string contents, IIOHelper ioHelper)
|
||||
{
|
||||
var absolutePath = ioHelper.MapPath(path);
|
||||
if (System.IO.File.Exists(absolutePath)) return;
|
||||
|
||||
using (var writer = System.IO.File.CreateText(absolutePath))
|
||||
{
|
||||
writer.Write(contents);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
public static IRelationType BuildEntity(RelationTypeDto dto)
|
||||
{
|
||||
var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ChildObjectType, dto.ParentObjectType);
|
||||
var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ParentObjectType, dto.ChildObjectType);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
@@ -77,5 +78,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <remarks>Here, <paramref name="filter"/> can be null but <paramref name="ordering"/> cannot.</remarks>
|
||||
IEnumerable<TEntity> GetPage(IQuery<TEntity> query, long pageIndex, int pageSize, out long totalRecords,
|
||||
IQuery<TEntity> filter, Ordering ordering);
|
||||
|
||||
ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options);
|
||||
}
|
||||
}
|
||||
|
||||
+125
-8
@@ -91,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// gets all version ids, current first
|
||||
public virtual IEnumerable<int> GetVersionIds(int nodeId, int maxRows)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersionIds", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersionIds, tsql =>
|
||||
tsql.Select<ContentVersionDto>(x => x.Id)
|
||||
.From<ContentVersionDto>()
|
||||
.Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId"))
|
||||
@@ -107,7 +107,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// TODO: test object node type?
|
||||
|
||||
// get the version we want to delete
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersion", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersion, tsql =>
|
||||
tsql.Select<ContentVersionDto>().From<ContentVersionDto>().Where<ContentVersionDto>(x => x.Id == SqlTemplate.Arg<int>("versionId"))
|
||||
);
|
||||
var versionDto = Database.Fetch<ContentVersionDto>(template.Sql(new { versionId })).FirstOrDefault();
|
||||
@@ -129,7 +129,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// TODO: test object node type?
|
||||
|
||||
// get the versions we want to delete, excluding the current one
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersions", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersions, tsql =>
|
||||
tsql.Select<ContentVersionDto>().From<ContentVersionDto>().Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg<DateTime>("versionDate"))
|
||||
);
|
||||
var versionDtos = Database.Fetch<ContentVersionDto>(template.Sql(new { nodeId, versionDate }));
|
||||
@@ -411,7 +411,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
// content type alias is invariant
|
||||
if(ordering.OrderBy.InvariantEquals("contentTypeAlias"))
|
||||
if (ordering.OrderBy.InvariantEquals("contentTypeAlias"))
|
||||
{
|
||||
var joins = Sql()
|
||||
.InnerJoin<ContentTypeDto>("ctype").On<ContentDto, ContentTypeDto>((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype");
|
||||
@@ -485,6 +485,123 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
IQuery<TEntity> filter,
|
||||
Ordering ordering);
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
var report = new Dictionary<int, ContentDataIntegrityReportEntry>();
|
||||
|
||||
var sql = SqlContext.Sql()
|
||||
.Select<NodeDto>()
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.OrderBy<NodeDto>(x => x.Level, x => x.ParentId, x => x.SortOrder);
|
||||
|
||||
var nodesToRebuild = new Dictionary<int, List<NodeDto>>();
|
||||
var validNodes = new Dictionary<int, NodeDto>();
|
||||
var rootIds = new[] {Constants.System.Root, Constants.System.RecycleBinContent, Constants.System.RecycleBinMedia};
|
||||
var currentParentIds = new HashSet<int>(rootIds);
|
||||
var prevParentIds = currentParentIds;
|
||||
var lastLevel = -1;
|
||||
|
||||
// use a forward cursor (query)
|
||||
foreach (var node in Database.Query<NodeDto>(sql))
|
||||
{
|
||||
if (node.Level != lastLevel)
|
||||
{
|
||||
// changing levels
|
||||
prevParentIds = currentParentIds;
|
||||
currentParentIds = null;
|
||||
lastLevel = node.Level;
|
||||
}
|
||||
|
||||
if (currentParentIds == null)
|
||||
{
|
||||
// we're reset
|
||||
currentParentIds = new HashSet<int>();
|
||||
}
|
||||
|
||||
currentParentIds.Add(node.NodeId);
|
||||
|
||||
// paths parts without the roots
|
||||
var pathParts = node.Path.Split(',').Where(x => !rootIds.Contains(int.Parse(x))).ToArray();
|
||||
|
||||
if (!prevParentIds.Contains(node.ParentId))
|
||||
{
|
||||
// invalid, this will be because the level is wrong (which prob means path is wrong too)
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathAndLevelByParentId));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else if (pathParts.Length == 0)
|
||||
{
|
||||
// invalid path
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathEmpty));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else if (pathParts.Length != node.Level)
|
||||
{
|
||||
// invalid, either path or level is wrong
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathLevelMismatch));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else if (pathParts[pathParts.Length - 1] != node.NodeId.ToString())
|
||||
{
|
||||
// invalid path
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathById));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else if (!rootIds.Contains(node.ParentId) && pathParts[pathParts.Length - 2] != node.ParentId.ToString())
|
||||
{
|
||||
// invalid path
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathByParentId));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's valid!
|
||||
|
||||
// don't track unless we are configured to fix
|
||||
if (options.FixIssues)
|
||||
validNodes.Add(node.NodeId, node);
|
||||
}
|
||||
}
|
||||
|
||||
var updated = new List<NodeDto>();
|
||||
|
||||
if (options.FixIssues)
|
||||
{
|
||||
// iterate all valid nodes to see if these are parents for invalid nodes
|
||||
foreach (var (nodeId, node) in validNodes)
|
||||
{
|
||||
if (!nodesToRebuild.TryGetValue(nodeId, out var invalidNodes)) continue;
|
||||
|
||||
// now we can try to rebuild the invalid paths.
|
||||
|
||||
foreach (var invalidNode in invalidNodes)
|
||||
{
|
||||
invalidNode.Level = (short)(node.Level + 1);
|
||||
invalidNode.Path = node.Path + "," + invalidNode.NodeId;
|
||||
updated.Add(invalidNode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var node in updated)
|
||||
{
|
||||
Database.Update(node);
|
||||
if (report.TryGetValue(node.NodeId, out var entry))
|
||||
entry.Fixed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new ContentDataIntegrityReport(report);
|
||||
}
|
||||
|
||||
private static void AppendNodeToFix(IDictionary<int, List<NodeDto>> nodesToRebuild, NodeDto node)
|
||||
{
|
||||
if (nodesToRebuild.TryGetValue(node.ParentId, out var childIds))
|
||||
childIds.Add(node);
|
||||
else
|
||||
nodesToRebuild[node.ParentId] = new List<NodeDto> { node };
|
||||
}
|
||||
|
||||
// here, filter can be null and ordering cannot
|
||||
protected IEnumerable<TEntity> GetPage<TDto>(IQuery<TEntity> query,
|
||||
long pageIndex, int pageSize, out long totalRecords,
|
||||
@@ -778,7 +895,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected virtual string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.EnsureUniqueNodeName", tsql => tsql
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.EnsureUniqueNodeName, tsql => tsql
|
||||
.Select<NodeDto>(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name"))
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType") && x.ParentId == SqlTemplate.Arg<int>("parentId")));
|
||||
@@ -791,7 +908,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected virtual int GetNewChildSortOrder(int parentId, int first)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetSortOrder", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql =>
|
||||
tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From<NodeDto>().Where<NodeDto>(x => x.ParentId == SqlTemplate.Arg<int>("parentId") && x.NodeObjectType == NodeObjectTypeId)
|
||||
);
|
||||
|
||||
@@ -800,7 +917,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected virtual NodeDto GetParentNodeDto(int parentId)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetParentNode", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql =>
|
||||
tsql.Select<NodeDto>().From<NodeDto>().Where<NodeDto>(x => x.NodeId == SqlTemplate.Arg<int>("parentId"))
|
||||
);
|
||||
|
||||
@@ -809,7 +926,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected virtual int GetReservedId(Guid uniqueId)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetReservedId", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql =>
|
||||
tsql.Select<NodeDto>(x => x.NodeId).From<NodeDto>().Where<NodeDto>(x => x.UniqueId == SqlTemplate.Arg<Guid>("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation)
|
||||
);
|
||||
var id = Database.ExecuteScalar<int?>(template.Sql(new { uniqueId = uniqueId }));
|
||||
|
||||
+158
-141
@@ -331,7 +331,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
.InnerJoin<DocumentVersionDto>()
|
||||
.On<ContentVersionDto, DocumentVersionDto>((c, d) => c.Id == d.Id)
|
||||
.Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg<DateTime>("versionDate"))
|
||||
.Where<DocumentVersionDto>( x => !x.Published)
|
||||
.Where<DocumentVersionDto>(x => !x.Published)
|
||||
);
|
||||
var versionDtos = Database.Fetch<ContentVersionDto>(template.Sql(new { nodeId, versionDate }));
|
||||
foreach (var versionDto in versionDtos)
|
||||
@@ -529,8 +529,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override void PersistUpdatedItem(IContent entity)
|
||||
{
|
||||
var entityBase = entity as EntityBase;
|
||||
var isEntityDirty = entityBase != null && entityBase.IsDirty();
|
||||
var isEntityDirty = entity.IsDirty();
|
||||
|
||||
// check if we need to make any database changes at all
|
||||
if ((entity.PublishedState == PublishedState.Published || entity.PublishedState == PublishedState.Unpublished)
|
||||
@@ -545,29 +544,41 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// update
|
||||
entity.UpdatingEntity();
|
||||
|
||||
// Check if this entity is being moved as a descendant as part of a bulk moving operations.
|
||||
// In this case we can bypass a lot of the below operations which will make this whole operation go much faster.
|
||||
// When moving we don't need to create new versions, etc... because we cannot roll this operation back anyways.
|
||||
var isMoving = entity.IsMoving();
|
||||
// TODO: I'm sure we can also detect a "Copy" (of a descendant) operation and probably perform similar checks below.
|
||||
// There is probably more stuff that would be required for copying but I'm sure not all of this logic would be, we could more than likely boost
|
||||
// copy performance by 95% just like we did for Move
|
||||
|
||||
|
||||
var publishing = entity.PublishedState == PublishedState.Publishing;
|
||||
|
||||
// check if we need to create a new version
|
||||
if (publishing && entity.PublishedVersionId > 0)
|
||||
if (!isMoving)
|
||||
{
|
||||
// published version is not published anymore
|
||||
Database.Execute(Sql().Update<DocumentVersionDto>(u => u.Set(x => x.Published, false)).Where<DocumentVersionDto>(x => x.Id == entity.PublishedVersionId));
|
||||
}
|
||||
// check if we need to create a new version
|
||||
if (publishing && entity.PublishedVersionId > 0)
|
||||
{
|
||||
// published version is not published anymore
|
||||
Database.Execute(Sql().Update<DocumentVersionDto>(u => u.Set(x => x.Published, false)).Where<DocumentVersionDto>(x => x.Id == entity.PublishedVersionId));
|
||||
}
|
||||
|
||||
// sanitize names
|
||||
SanitizeNames(entity, publishing);
|
||||
// sanitize names
|
||||
SanitizeNames(entity, publishing);
|
||||
|
||||
// ensure that strings don't contain characters that are invalid in xml
|
||||
// TODO: do we really want to keep doing this here?
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
// ensure that strings don't contain characters that are invalid in xml
|
||||
// TODO: do we really want to keep doing this here?
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty("ParentId"))
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty("ParentId"))
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// create the dto
|
||||
@@ -578,146 +589,152 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
nodeDto.ValidatePathWithException();
|
||||
Database.Update(nodeDto);
|
||||
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
|
||||
// update the content & document version dtos
|
||||
var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto;
|
||||
var documentVersionDto = dto.DocumentVersionDto;
|
||||
if (publishing)
|
||||
if (!isMoving)
|
||||
{
|
||||
documentVersionDto.Published = true; // now published
|
||||
contentVersionDto.Current = false; // no more current
|
||||
}
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(documentVersionDto);
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
|
||||
// and, if publishing, insert new content & document version dtos
|
||||
if (publishing)
|
||||
{
|
||||
entity.PublishedVersionId = entity.VersionId;
|
||||
|
||||
contentVersionDto.Id = 0; // want a new id
|
||||
contentVersionDto.Current = true; // current version
|
||||
contentVersionDto.Text = entity.Name;
|
||||
Database.Insert(contentVersionDto);
|
||||
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
|
||||
|
||||
documentVersionDto.Published = false; // non-published version
|
||||
Database.Insert(documentVersionDto);
|
||||
}
|
||||
|
||||
// replace the property data (rather than updating)
|
||||
// only need to delete for the version that existed, the new version (if any) has no property data yet
|
||||
var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId;
|
||||
var deletePropertyDataSql = Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == versionToDelete);
|
||||
Database.Execute(deletePropertyDataSql);
|
||||
|
||||
// insert property data
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0,
|
||||
entity.Properties, LanguageRepository, out var edited, out var editedCultures);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
|
||||
// if !publishing, we may have a new name != current publish name,
|
||||
// also impacts 'edited'
|
||||
if (!publishing && entity.PublishName != entity.Name)
|
||||
edited = true;
|
||||
|
||||
if (entity.ContentType.VariesByCulture())
|
||||
{
|
||||
// bump dates to align cultures to version
|
||||
// update the content & document version dtos
|
||||
var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto;
|
||||
var documentVersionDto = dto.DocumentVersionDto;
|
||||
if (publishing)
|
||||
entity.AdjustDates(contentVersionDto.VersionDate);
|
||||
{
|
||||
documentVersionDto.Published = true; // now published
|
||||
contentVersionDto.Current = false; // no more current
|
||||
}
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(documentVersionDto);
|
||||
|
||||
// names also impact 'edited'
|
||||
// ReSharper disable once UseDeconstruction
|
||||
foreach (var cultureInfo in entity.CultureInfos)
|
||||
if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture))
|
||||
{
|
||||
edited = true;
|
||||
(editedCultures ?? (editedCultures = new HashSet<string>(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture);
|
||||
// and, if publishing, insert new content & document version dtos
|
||||
if (publishing)
|
||||
{
|
||||
entity.PublishedVersionId = entity.VersionId;
|
||||
|
||||
// TODO: change tracking
|
||||
// at the moment, we don't do any dirty tracking on property values, so we don't know whether the
|
||||
// culture has just been edited or not, so we don't update its update date - that date only changes
|
||||
// when the name is set, and it all works because the controller does it - but, if someone uses a
|
||||
// service to change a property value and save (without setting name), the update date does not change.
|
||||
}
|
||||
contentVersionDto.Id = 0; // want a new id
|
||||
contentVersionDto.Current = true; // current version
|
||||
contentVersionDto.Text = entity.Name;
|
||||
Database.Insert(contentVersionDto);
|
||||
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
|
||||
|
||||
// replace the content version variations (rather than updating)
|
||||
documentVersionDto.Published = false; // non-published version
|
||||
Database.Insert(documentVersionDto);
|
||||
}
|
||||
|
||||
// replace the property data (rather than updating)
|
||||
// only need to delete for the version that existed, the new version (if any) has no property data yet
|
||||
var deleteContentVariations = Sql().Delete<ContentVersionCultureVariationDto>().Where<ContentVersionCultureVariationDto>(x => x.VersionId == versionToDelete);
|
||||
Database.Execute(deleteContentVariations);
|
||||
var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId;
|
||||
var deletePropertyDataSql = Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == versionToDelete);
|
||||
Database.Execute(deletePropertyDataSql);
|
||||
|
||||
// replace the document version variations (rather than updating)
|
||||
var deleteDocumentVariations = Sql().Delete<DocumentCultureVariationDto>().Where<DocumentCultureVariationDto>(x => x.NodeId == entity.Id);
|
||||
Database.Execute(deleteDocumentVariations);
|
||||
// insert property data
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0,
|
||||
entity.Properties, LanguageRepository, out var edited, out var editedCultures);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
|
||||
// TODO: NPoco InsertBulk issue?
|
||||
// we should use the native NPoco InsertBulk here but it causes problems (not sure exactly all scenarios)
|
||||
// but by using SQL Server and updating a variants name will cause: Unable to cast object of type
|
||||
// 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'.
|
||||
// (same in PersistNewItem above)
|
||||
// if !publishing, we may have a new name != current publish name,
|
||||
// also impacts 'edited'
|
||||
if (!publishing && entity.PublishName != entity.Name)
|
||||
edited = true;
|
||||
|
||||
// insert content variations
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
if (entity.ContentType.VariesByCulture())
|
||||
{
|
||||
// bump dates to align cultures to version
|
||||
if (publishing)
|
||||
entity.AdjustDates(contentVersionDto.VersionDate);
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
// names also impact 'edited'
|
||||
// ReSharper disable once UseDeconstruction
|
||||
foreach (var cultureInfo in entity.CultureInfos)
|
||||
if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture))
|
||||
{
|
||||
edited = true;
|
||||
(editedCultures ?? (editedCultures = new HashSet<string>(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture);
|
||||
|
||||
// TODO: change tracking
|
||||
// at the moment, we don't do any dirty tracking on property values, so we don't know whether the
|
||||
// culture has just been edited or not, so we don't update its update date - that date only changes
|
||||
// when the name is set, and it all works because the controller does it - but, if someone uses a
|
||||
// service to change a property value and save (without setting name), the update date does not change.
|
||||
}
|
||||
|
||||
// replace the content version variations (rather than updating)
|
||||
// only need to delete for the version that existed, the new version (if any) has no property data yet
|
||||
var deleteContentVariations = Sql().Delete<ContentVersionCultureVariationDto>().Where<ContentVersionCultureVariationDto>(x => x.VersionId == versionToDelete);
|
||||
Database.Execute(deleteContentVariations);
|
||||
|
||||
// replace the document version variations (rather than updating)
|
||||
var deleteDocumentVariations = Sql().Delete<DocumentCultureVariationDto>().Where<DocumentCultureVariationDto>(x => x.NodeId == entity.Id);
|
||||
Database.Execute(deleteDocumentVariations);
|
||||
|
||||
// TODO: NPoco InsertBulk issue?
|
||||
// we should use the native NPoco InsertBulk here but it causes problems (not sure exactly all scenarios)
|
||||
// but by using SQL Server and updating a variants name will cause: Unable to cast object of type
|
||||
// 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'.
|
||||
// (same in PersistNewItem above)
|
||||
|
||||
// insert content variations
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
entity.SetCultureEdited(editedCultures);
|
||||
|
||||
// update the document dto
|
||||
// at that point, when un/publishing, the entity still has its old Published value
|
||||
// so we need to explicitly update the dto to persist the correct value
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
dto.Published = true;
|
||||
else if (entity.PublishedState == PublishedState.Unpublishing)
|
||||
dto.Published = false;
|
||||
entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
|
||||
Database.Update(dto);
|
||||
|
||||
//update the schedule
|
||||
if (entity.IsPropertyDirty("ContentSchedule"))
|
||||
PersistContentSchedule(entity, true);
|
||||
|
||||
// if entity is publishing, update tags, else leave tags there
|
||||
// means that implicitly unpublished, or trashed, entities *still* have tags in db
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
// refresh content
|
||||
entity.SetCultureEdited(editedCultures);
|
||||
|
||||
// update the document dto
|
||||
// at that point, when un/publishing, the entity still has its old Published value
|
||||
// so we need to explicitly update the dto to persist the correct value
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
dto.Published = true;
|
||||
else if (entity.PublishedState == PublishedState.Unpublishing)
|
||||
dto.Published = false;
|
||||
entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
|
||||
Database.Update(dto);
|
||||
|
||||
//update the schedule
|
||||
if (entity.IsPropertyDirty("ContentSchedule"))
|
||||
PersistContentSchedule(entity, true);
|
||||
|
||||
// if entity is publishing, update tags, else leave tags there
|
||||
// means that implicitly unpublished, or trashed, entities *still* have tags in db
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
|
||||
// trigger here, before we reset Published etc
|
||||
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
|
||||
|
||||
// flip the entity's published property
|
||||
// this also flips its published state
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
if (!isMoving)
|
||||
{
|
||||
entity.Published = true;
|
||||
entity.PublishTemplateId = entity.TemplateId;
|
||||
entity.PublisherId = entity.WriterId;
|
||||
entity.PublishName = entity.Name;
|
||||
entity.PublishDate = entity.UpdateDate;
|
||||
// flip the entity's published property
|
||||
// this also flips its published state
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
{
|
||||
entity.Published = true;
|
||||
entity.PublishTemplateId = entity.TemplateId;
|
||||
entity.PublisherId = entity.WriterId;
|
||||
entity.PublishName = entity.Name;
|
||||
entity.PublishDate = entity.UpdateDate;
|
||||
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
}
|
||||
else if (entity.PublishedState == PublishedState.Unpublishing)
|
||||
{
|
||||
entity.Published = false;
|
||||
entity.PublishTemplateId = null;
|
||||
entity.PublisherId = null;
|
||||
entity.PublishName = null;
|
||||
entity.PublishDate = null;
|
||||
|
||||
ClearEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
|
||||
}
|
||||
else if (entity.PublishedState == PublishedState.Unpublishing)
|
||||
{
|
||||
entity.Published = false;
|
||||
entity.PublishTemplateId = null;
|
||||
entity.PublisherId = null;
|
||||
entity.PublishName = null;
|
||||
entity.PublishDate = null;
|
||||
|
||||
ClearEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
|
||||
@@ -231,7 +231,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override void PersistNewItem(IMedia entity)
|
||||
{
|
||||
var media = (Models.Media) entity;
|
||||
entity.AddingEntity();
|
||||
|
||||
// ensure unique name on the same level
|
||||
@@ -286,15 +285,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
contentVersionDto.NodeId = nodeDto.NodeId;
|
||||
contentVersionDto.Current = true;
|
||||
Database.Insert(contentVersionDto);
|
||||
media.VersionId = contentVersionDto.Id;
|
||||
entity.VersionId = contentVersionDto.Id;
|
||||
|
||||
// persist the media version dto
|
||||
var mediaVersionDto = dto.MediaVersionDto;
|
||||
mediaVersionDto.Id = media.VersionId;
|
||||
mediaVersionDto.Id = entity.VersionId;
|
||||
Database.Insert(mediaVersionDto);
|
||||
|
||||
// persist the property data
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(media.ContentType.Variations, media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
|
||||
@@ -310,26 +309,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override void PersistUpdatedItem(IMedia entity)
|
||||
{
|
||||
var media = (Models.Media) entity;
|
||||
|
||||
// update
|
||||
media.UpdatingEntity();
|
||||
entity.UpdatingEntity();
|
||||
|
||||
// ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
// Check if this entity is being moved as a descendant as part of a bulk moving operations.
|
||||
// In this case we can bypass a lot of the below operations which will make this whole operation go much faster.
|
||||
// When moving we don't need to create new versions, etc... because we cannot roll this operation back anyways.
|
||||
var isMoving = entity.IsMoving();
|
||||
|
||||
// ensure that strings don't contain characters that are invalid in xml
|
||||
// TODO: do we really want to keep doing this here?
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty("ParentId"))
|
||||
if (!isMoving)
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
// ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
// ensure that strings don't contain characters that are invalid in xml
|
||||
// TODO: do we really want to keep doing this here?
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty(nameof(entity.ParentId)))
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// create the dto
|
||||
@@ -340,26 +345,29 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
nodeDto.ValidatePathWithException();
|
||||
Database.Update(nodeDto);
|
||||
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
if (!isMoving)
|
||||
{
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
|
||||
// update the content & media version dtos
|
||||
var contentVersionDto = dto.MediaVersionDto.ContentVersionDto;
|
||||
var mediaVersionDto = dto.MediaVersionDto;
|
||||
contentVersionDto.Current = true;
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(mediaVersionDto);
|
||||
// update the content & media version dtos
|
||||
var contentVersionDto = dto.MediaVersionDto.ContentVersionDto;
|
||||
var mediaVersionDto = dto.MediaVersionDto;
|
||||
contentVersionDto.Current = true;
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(mediaVersionDto);
|
||||
|
||||
// replace the property data
|
||||
var deletePropertyDataSql = SqlContext.Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == media.VersionId);
|
||||
Database.Execute(deletePropertyDataSql);
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(media.ContentType.Variations, media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
// replace the property data
|
||||
var deletePropertyDataSql = SqlContext.Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == entity.VersionId);
|
||||
Database.Execute(deletePropertyDataSql);
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
|
||||
PersistRelations(entity);
|
||||
PersistRelations(entity);
|
||||
}
|
||||
|
||||
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace Umbraco.Core.PropertyEditors
|
||||
public class DataEditor : IDataEditor
|
||||
{
|
||||
private IDictionary<string, object> _defaultConfiguration;
|
||||
private IDataValueEditor _dataValueEditor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataEditor"/> class.
|
||||
@@ -105,7 +104,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// simple enough for now.</para>
|
||||
/// </remarks>
|
||||
// TODO: point of that one? shouldn't we always configure?
|
||||
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_dataValueEditor ?? (_dataValueEditor = CreateValueEditor()));
|
||||
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
|
||||
@@ -186,6 +186,10 @@ namespace Umbraco.Web.PropertyEditors
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(object value)
|
||||
{
|
||||
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
|
||||
|
||||
if (rawJson.IsNullOrWhiteSpace())
|
||||
yield break;
|
||||
|
||||
DeserializeGridValue(rawJson, out var richTextEditorValues, out var mediaValues);
|
||||
|
||||
foreach (var umbracoEntityReference in richTextEditorValues.SelectMany(x =>
|
||||
|
||||
@@ -57,9 +57,11 @@ namespace Umbraco.Web.PropertyEditors
|
||||
|
||||
if (string.IsNullOrEmpty(asString)) yield break;
|
||||
|
||||
|
||||
if (UdiParser.TryParse(asString, out var udi))
|
||||
yield return new UmbracoEntityReference(udi);
|
||||
foreach (var udiStr in asString.Split(','))
|
||||
{
|
||||
if (UdiParser.TryParse(udiStr, out var udi))
|
||||
yield return new UmbracoEntityReference(udi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Examine;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Composing.CompositionExtensions;
|
||||
@@ -7,9 +8,11 @@ using Umbraco.Core.Configuration.Grid;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Migrations.PostMigrations;
|
||||
@@ -17,19 +20,36 @@ using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Examine;
|
||||
using Umbraco.Infrastructure.Media;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Actions;
|
||||
using Umbraco.Web.Cache;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Features;
|
||||
using Umbraco.Web.HealthCheck;
|
||||
using Umbraco.Web.HealthCheck.NotificationMethods;
|
||||
using Umbraco.Web.Install;
|
||||
using Umbraco.Web.Macros;
|
||||
using Umbraco.Web.Media.EmbedProviders;
|
||||
using Umbraco.Web.Migrations.PostMigrations;
|
||||
using Umbraco.Web.Models.PublishedContent;
|
||||
using Umbraco.Web.PropertyEditors;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Search;
|
||||
using Umbraco.Web.Sections;
|
||||
using Umbraco.Web.Services;
|
||||
using Umbraco.Web.Templates;
|
||||
using Umbraco.Web.Trees;
|
||||
using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator;
|
||||
|
||||
@@ -42,7 +62,7 @@ namespace Umbraco.Core.Runtime
|
||||
public override void Compose(Composition composition)
|
||||
{
|
||||
base.Compose(composition);
|
||||
|
||||
|
||||
// composers
|
||||
composition
|
||||
.ComposeRepositories()
|
||||
@@ -177,6 +197,161 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
// Config manipulator
|
||||
composition.RegisterUnique<IConfigManipulator, JsonConfigManipulator>();
|
||||
|
||||
|
||||
// register the http context and umbraco context accessors
|
||||
// we *should* use the HttpContextUmbracoContextAccessor, however there are cases when
|
||||
// we have no http context, eg when booting Umbraco or in background threads, so instead
|
||||
// let's use an hybrid accessor that can fall back to a ThreadStatic context.
|
||||
composition.RegisterUnique<IUmbracoContextAccessor, HybridUmbracoContextAccessor>();
|
||||
|
||||
// register the umbraco context factory
|
||||
// composition.RegisterUnique<IUmbracoContextFactory, UmbracoContextFactory>();
|
||||
composition.RegisterUnique<IPublishedUrlProvider, UrlProvider>();
|
||||
|
||||
composition.RegisterUnique<HtmlLocalLinkParser>();
|
||||
composition.RegisterUnique<HtmlImageSourceParser>();
|
||||
composition.RegisterUnique<HtmlUrlParser>();
|
||||
composition.RegisterUnique<RichTextEditorPastedImages>();
|
||||
|
||||
// both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be
|
||||
// discovered when CoreBootManager configures the converters. We HAVE to remove one of them
|
||||
// here because there cannot be two converters for one property editor - and we want the full
|
||||
// RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter.
|
||||
// (the limited one, defined in Core, is there for tests) - same for others
|
||||
composition.PropertyValueConverters()
|
||||
.Remove<TinyMceValueConverter>()
|
||||
.Remove<TextStringValueConverter>()
|
||||
.Remove<MarkdownEditorValueConverter>();
|
||||
|
||||
composition.UrlProviders()
|
||||
.Append<AliasUrlProvider>()
|
||||
.Append<DefaultUrlProvider>();
|
||||
|
||||
composition.MediaUrlProviders()
|
||||
.Append<DefaultMediaUrlProvider>();
|
||||
|
||||
composition.RegisterUnique<ISiteDomainHelper, SiteDomainHelper>();
|
||||
|
||||
// register properties fallback
|
||||
composition.RegisterUnique<IPublishedValueFallback, PublishedValueFallback>();
|
||||
|
||||
composition.RegisterUnique<IImageUrlGenerator, ImageSharpImageUrlGenerator>();
|
||||
|
||||
composition.RegisterUnique<UmbracoFeatures>();
|
||||
|
||||
composition.Actions()
|
||||
.Add(() => composition.TypeLoader.GetTypes<IAction>());
|
||||
|
||||
composition.EditorValidators()
|
||||
.Add(() => composition.TypeLoader.GetTypes<IEditorValidator>());
|
||||
|
||||
|
||||
composition.TourFilters();
|
||||
|
||||
// replace with web implementation
|
||||
composition.RegisterUnique<IPublishedSnapshotRebuilder, PublishedSnapshotRebuilder>();
|
||||
|
||||
// register OEmbed providers - no type scanning - all explicit opt-in of adding types
|
||||
// note: IEmbedProvider is not IDiscoverable - think about it if going for type scanning
|
||||
composition.OEmbedProviders()
|
||||
.Append<YouTube>()
|
||||
.Append<Instagram>()
|
||||
.Append<Twitter>()
|
||||
.Append<Vimeo>()
|
||||
.Append<DailyMotion>()
|
||||
.Append<Flickr>()
|
||||
.Append<Slideshare>()
|
||||
.Append<Kickstarter>()
|
||||
.Append<GettyImages>()
|
||||
.Append<Ted>()
|
||||
.Append<Soundcloud>()
|
||||
.Append<Issuu>()
|
||||
.Append<Hulu>()
|
||||
.Append<Giphy>();
|
||||
|
||||
// register back office sections in the order we want them rendered
|
||||
composition.Sections()
|
||||
.Append<ContentSection>()
|
||||
.Append<MediaSection>()
|
||||
.Append<SettingsSection>()
|
||||
.Append<PackagesSection>()
|
||||
.Append<UsersSection>()
|
||||
.Append<MembersSection>()
|
||||
.Append<FormsSection>()
|
||||
.Append<TranslationSection>();
|
||||
|
||||
// register known content apps
|
||||
composition.ContentApps()
|
||||
.Append<ListViewContentAppFactory>()
|
||||
.Append<ContentEditorContentAppFactory>()
|
||||
.Append<ContentInfoContentAppFactory>();
|
||||
|
||||
// register published router
|
||||
composition.RegisterUnique<IPublishedRouter, PublishedRouter>();
|
||||
|
||||
// register *all* checks, except those marked [HideFromTypeFinder] of course
|
||||
composition.HealthChecks()
|
||||
.Add(() => composition.TypeLoader.GetTypes<HealthCheck>());
|
||||
|
||||
|
||||
composition.WithCollectionBuilder<HealthCheckNotificationMethodCollectionBuilder>()
|
||||
.Add(() => composition.TypeLoader.GetTypes<IHealthCheckNotificationMethod>());
|
||||
|
||||
composition.RegisterUnique<IContentLastChanceFinder, ContentFinderByConfigured404>();
|
||||
|
||||
composition.ContentFinders()
|
||||
// all built-in finders in the correct order,
|
||||
// devs can then modify this list on application startup
|
||||
.Append<ContentFinderByPageIdQuery>()
|
||||
.Append<ContentFinderByUrl>()
|
||||
.Append<ContentFinderByIdPath>()
|
||||
//.Append<ContentFinderByUrlAndTemplate>() // disabled, this is an odd finder
|
||||
.Append<ContentFinderByUrlAlias>()
|
||||
.Append<ContentFinderByRedirectUrl>();
|
||||
|
||||
composition.Register<UmbracoTreeSearcher>(Lifetime.Request);
|
||||
|
||||
composition.SearchableTrees()
|
||||
.Add(() => composition.TypeLoader.GetTypes<ISearchableTree>());
|
||||
|
||||
// replace some services
|
||||
composition.RegisterUnique<IEventMessagesFactory, DefaultEventMessagesFactory>();
|
||||
composition.RegisterUnique<IEventMessagesAccessor, HybridEventMessagesAccessor>();
|
||||
composition.RegisterUnique<ITreeService, TreeService>();
|
||||
composition.RegisterUnique<ISectionService, SectionService>();
|
||||
|
||||
composition.RegisterUnique<IExamineManager, ExamineManager>();
|
||||
|
||||
// register distributed cache
|
||||
composition.RegisterUnique(f => new DistributedCache(f.GetInstance<IServerMessenger>(), f.GetInstance<CacheRefresherCollection>()));
|
||||
|
||||
|
||||
composition.Register<ITagQuery, TagQuery>(Lifetime.Request);
|
||||
|
||||
composition.RegisterUnique<HtmlLocalLinkParser>();
|
||||
composition.RegisterUnique<HtmlUrlParser>();
|
||||
composition.RegisterUnique<HtmlImageSourceParser>();
|
||||
composition.RegisterUnique<RichTextEditorPastedImages>();
|
||||
|
||||
composition.RegisterUnique<IUmbracoTreeSearcherFields, UmbracoTreeSearcherFields>();
|
||||
composition.Register<IPublishedContentQuery>(factory =>
|
||||
{
|
||||
var umbCtx = factory.GetInstance<IUmbracoContextAccessor>();
|
||||
return new PublishedContentQuery(umbCtx.UmbracoContext.PublishedSnapshot, factory.GetInstance<IVariationContextAccessor>(), factory.GetInstance<IExamineManager>());
|
||||
}, Lifetime.Request);
|
||||
|
||||
|
||||
composition.RegisterUnique<IPublishedUrlProvider, UrlProvider>();
|
||||
|
||||
// register the http context and umbraco context accessors
|
||||
// we *should* use the HttpContextUmbracoContextAccessor, however there are cases when
|
||||
// we have no http context, eg when booting Umbraco or in background threads, so instead
|
||||
// let's use an hybrid accessor that can fall back to a ThreadStatic context.
|
||||
composition.RegisterUnique<IUmbracoContextAccessor, HybridUmbracoContextAccessor>();
|
||||
|
||||
// register accessors for cultures
|
||||
composition.RegisterUnique<IDefaultCultureAccessor, DefaultCultureAccessor>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Umbraco.Core.Runtime
|
||||
private IFactory _factory;
|
||||
private readonly RuntimeState _state;
|
||||
private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IConnectionStrings _connectionStrings;
|
||||
|
||||
@@ -39,7 +40,8 @@ namespace Umbraco.Core.Runtime
|
||||
IBackOfficeInfo backOfficeInfo,
|
||||
IDbProviderFactoryCreator dbProviderFactoryCreator,
|
||||
IMainDom mainDom,
|
||||
ITypeFinder typeFinder)
|
||||
ITypeFinder typeFinder,
|
||||
IRequestCache requestCache)
|
||||
{
|
||||
IOHelper = ioHelper;
|
||||
Configs = configs;
|
||||
@@ -50,6 +52,7 @@ namespace Umbraco.Core.Runtime
|
||||
DbProviderFactoryCreator = dbProviderFactoryCreator;
|
||||
|
||||
_umbracoBootPermissionChecker = umbracoBootPermissionChecker;
|
||||
_requestCache = requestCache;
|
||||
|
||||
Logger = logger;
|
||||
MainDom = mainDom;
|
||||
@@ -110,6 +113,7 @@ namespace Umbraco.Core.Runtime
|
||||
{
|
||||
if (register is null) throw new ArgumentNullException(nameof(register));
|
||||
|
||||
|
||||
// create and register the essential services
|
||||
// ie the bare minimum required to boot
|
||||
|
||||
@@ -129,12 +133,19 @@ namespace Umbraco.Core.Runtime
|
||||
"Booted.",
|
||||
"Boot failed."))
|
||||
{
|
||||
Logger.Info<CoreRuntime>("Booting Core");
|
||||
|
||||
Logger.Info<CoreRuntime>("Booting site '{HostingSiteName}', app '{HostingApplicationId}', path '{HostingPhysicalPath}', server '{MachineName}'.",
|
||||
HostingEnvironment?.SiteName,
|
||||
HostingEnvironment?.ApplicationId,
|
||||
HostingEnvironment?.ApplicationPhysicalPath,
|
||||
NetworkHelper.MachineName);
|
||||
Logger.Debug<CoreRuntime>("Runtime: {Runtime}", GetType().FullName);
|
||||
|
||||
// application environment
|
||||
ConfigureUnhandledException();
|
||||
return _factory = Configure(register, timer);
|
||||
_factory = Configure(register, timer);
|
||||
|
||||
return _factory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +162,7 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
|
||||
// run handlers
|
||||
RuntimeOptions.DoRuntimeBoot(ProfilingLogger);
|
||||
@@ -244,6 +255,13 @@ namespace Umbraco.Core.Runtime
|
||||
// create & initialize the components
|
||||
_components = _factory.GetInstance<ComponentCollection>();
|
||||
_components.Initialize();
|
||||
|
||||
|
||||
// now (and only now) is the time to switch over to perWebRequest scopes.
|
||||
// up until that point we may not have a request, and scoped services would
|
||||
// fail to resolve - but we run Initialize within a factory scope - and then,
|
||||
// here, we switch the factory to bind scopes to requests
|
||||
_factory.EnablePerWebRequestScope();
|
||||
}
|
||||
|
||||
protected virtual void ConfigureUnhandledException()
|
||||
@@ -350,7 +368,7 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
return new AppCaches(
|
||||
new DeepCloneAppCache(new ObjectCacheAppCache()),
|
||||
NoAppCache.Instance,
|
||||
_requestCache,
|
||||
new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache())));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Runtime;
|
||||
|
||||
namespace Umbraco.Web.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Web Umbraco runtime.
|
||||
/// </summary>
|
||||
/// <remarks>On top of CoreRuntime, handles all of the web-related runtime aspects of Umbraco.</remarks>
|
||||
public class WebRuntime : CoreRuntime
|
||||
{
|
||||
private readonly IRequestCache _requestCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WebRuntime"/> class.
|
||||
/// </summary>
|
||||
public WebRuntime(
|
||||
Configs configs,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IIOHelper ioHelper,
|
||||
ILogger logger,
|
||||
IProfiler profiler,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IBackOfficeInfo backOfficeInfo,
|
||||
IDbProviderFactoryCreator dbProviderFactoryCreator,
|
||||
IMainDom mainDom,
|
||||
ITypeFinder typeFinder,
|
||||
IRequestCache requestCache,
|
||||
IUmbracoBootPermissionChecker umbracoBootPermissionChecker):
|
||||
base(configs, umbracoVersion, ioHelper, logger, profiler ,umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder)
|
||||
{
|
||||
_requestCache = requestCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IFactory Configure(IRegister register)
|
||||
{
|
||||
|
||||
var profilingLogger = new ProfilingLogger(Logger, Profiler);
|
||||
var umbracoVersion = new UmbracoVersion();
|
||||
using (var timer = profilingLogger.TraceDuration<CoreRuntime>(
|
||||
$"Booting Umbraco {umbracoVersion.SemanticVersion.ToSemanticString()}.",
|
||||
"Booted.",
|
||||
"Boot failed."))
|
||||
{
|
||||
Logger.Info<CoreRuntime>("Booting site '{HostingSiteName}', app '{HostingApplicationId}', path '{HostingPhysicalPath}', server '{MachineName}'.",
|
||||
HostingEnvironment.SiteName,
|
||||
HostingEnvironment.ApplicationId,
|
||||
HostingEnvironment.ApplicationPhysicalPath,
|
||||
NetworkHelper.MachineName);
|
||||
Logger.Debug<CoreRuntime>("Runtime: {Runtime}", GetType().FullName);
|
||||
|
||||
var factory = base.Configure(register);
|
||||
|
||||
// now (and only now) is the time to switch over to perWebRequest scopes.
|
||||
// up until that point we may not have a request, and scoped services would
|
||||
// fail to resolve - but we run Initialize within a factory scope - and then,
|
||||
// here, we switch the factory to bind scopes to requests
|
||||
factory.EnablePerWebRequestScope();
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#region Getters
|
||||
|
||||
protected override AppCaches GetAppCaches() => new AppCaches(
|
||||
// we need to have the dep clone runtime cache provider to ensure
|
||||
// all entities are cached properly (cloned in and cloned out)
|
||||
new DeepCloneAppCache(new ObjectCacheAppCache()),
|
||||
// we need request based cache when running in web-based context
|
||||
_requestCache,
|
||||
new IsolatedCaches(type =>
|
||||
// we need to have the dep clone runtime cache provider to ensure
|
||||
// all entities are cached properly (cloned in and cloned out)
|
||||
new DeepCloneAppCache(new ObjectCacheAppCache())));
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
@@ -27,6 +28,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
private readonly IDocumentBlueprintRepository _documentBlueprintRepository;
|
||||
private readonly ILanguageRepository _languageRepository;
|
||||
private readonly Lazy<IPropertyValidationService> _propertyValidationService;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private IQuery<IContent> _queryNotTrashed;
|
||||
|
||||
#region Constructors
|
||||
@@ -35,7 +37,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
IEventMessagesFactory eventMessagesFactory,
|
||||
IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository,
|
||||
IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository,
|
||||
Lazy<IPropertyValidationService> propertyValidationService)
|
||||
Lazy<IPropertyValidationService> propertyValidationService, IShortStringHelper shortStringHelper)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_documentRepository = documentRepository;
|
||||
@@ -45,6 +47,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
_documentBlueprintRepository = documentBlueprintRepository;
|
||||
_languageRepository = languageRepository;
|
||||
_propertyValidationService = propertyValidationService;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -600,23 +603,27 @@ namespace Umbraco.Core.Services.Implement
|
||||
totalChildren = 0;
|
||||
return Enumerable.Empty<IContent>();
|
||||
}
|
||||
return GetPagedDescendantsLocked(contentPath[0].Path, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
return GetPagedLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IContent> GetPagedDescendantsLocked(string contentPath, long pageIndex, int pageSize, out long totalChildren,
|
||||
private IQuery<IContent> GetPagedDescendantQuery(string contentPath)
|
||||
{
|
||||
var query = Query<IContent>();
|
||||
if (!contentPath.IsNullOrWhiteSpace())
|
||||
query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar));
|
||||
return query;
|
||||
}
|
||||
|
||||
private IEnumerable<IContent> GetPagedLocked(IQuery<IContent> query, long pageIndex, int pageSize, out long totalChildren,
|
||||
IQuery<IContent> filter, Ordering ordering)
|
||||
{
|
||||
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
|
||||
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
|
||||
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
|
||||
|
||||
var query = Query<IContent>();
|
||||
if (!contentPath.IsNullOrWhiteSpace())
|
||||
query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar));
|
||||
|
||||
return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
|
||||
@@ -1865,7 +1872,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
public OperationResult MoveToRecycleBin(IContent content, int userId)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
var moves = new List<Tuple<IContent, string>>();
|
||||
var moves = new List<(IContent, string)>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -1924,7 +1931,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
return;
|
||||
}
|
||||
|
||||
var moves = new List<Tuple<IContent, string>>();
|
||||
var moves = new List<(IContent, string)>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -1977,7 +1984,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// MUST be called from within WriteLock
|
||||
// trash indicates whether we are trashing, un-trashing, or not changing anything
|
||||
private void PerformMoveLocked(IContent content, int parentId, IContent parent, int userId,
|
||||
ICollection<Tuple<IContent, string>> moves,
|
||||
ICollection<(IContent, string)> moves,
|
||||
bool? trash)
|
||||
{
|
||||
content.WriterId = userId;
|
||||
@@ -1989,7 +1996,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var paths = new Dictionary<int, string>();
|
||||
|
||||
moves.Add(Tuple.Create(content, content.Path)); // capture original path
|
||||
moves.Add((content, content.Path)); // capture original path
|
||||
|
||||
//need to store the original path to lookup descendants based on it below
|
||||
var originalPath = content.Path;
|
||||
@@ -2006,20 +2013,24 @@ namespace Umbraco.Core.Services.Implement
|
||||
paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString) : parent.Path) + "," + content.Id;
|
||||
|
||||
const int pageSize = 500;
|
||||
var total = long.MaxValue;
|
||||
while (total > 0)
|
||||
var query = GetPagedDescendantQuery(originalPath);
|
||||
long total;
|
||||
do
|
||||
{
|
||||
var descendants = GetPagedDescendantsLocked(originalPath, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
|
||||
// We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
|
||||
var descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
|
||||
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path
|
||||
moves.Add((descendant, descendant.Path)); // capture original path
|
||||
|
||||
// update path and level since we do not update parentId
|
||||
descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
|
||||
descendant.Level += levelDelta;
|
||||
PerformMoveContentLocked(descendant, userId, trash);
|
||||
}
|
||||
}
|
||||
|
||||
} while (total > pageSize);
|
||||
|
||||
}
|
||||
|
||||
@@ -2367,6 +2378,25 @@ namespace Umbraco.Core.Services.Implement
|
||||
return OperationResult.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var report = _documentRepository.CheckDataIntegrity(options);
|
||||
|
||||
if (report.FixedIssues.Count > 0)
|
||||
{
|
||||
//The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
|
||||
var root = new Content("root", -1, new ContentType(_shortStringHelper, -1)) {Id = -1, Key = Guid.Empty};
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>.EventArgs(new TreeChange<IContent>(root, TreeChangeTypes.RefreshAll)));
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
@@ -2804,7 +2834,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
|
||||
|
||||
var changes = new List<TreeChange<IContent>>();
|
||||
var moves = new List<Tuple<IContent, string>>();
|
||||
var moves = new List<(IContent, string)>();
|
||||
var contentTypeIdsA = contentTypeIds.ToArray();
|
||||
|
||||
// using an immediate uow here because we keep making changes with
|
||||
|
||||
@@ -13,6 +13,7 @@ using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
@@ -25,6 +26,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
private readonly IMediaTypeRepository _mediaTypeRepository;
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
private readonly IEntityRepository _entityRepository;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
private readonly IMediaFileSystem _mediaFileSystem;
|
||||
|
||||
@@ -32,7 +34,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
public MediaService(IScopeProvider provider, IMediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IMediaRepository mediaRepository, IAuditRepository auditRepository, IMediaTypeRepository mediaTypeRepository,
|
||||
IEntityRepository entityRepository)
|
||||
IEntityRepository entityRepository, IShortStringHelper shortStringHelper)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
@@ -40,6 +42,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
_auditRepository = auditRepository;
|
||||
_mediaTypeRepository = mediaTypeRepository;
|
||||
_entityRepository = entityRepository;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -530,23 +533,27 @@ namespace Umbraco.Core.Services.Implement
|
||||
totalChildren = 0;
|
||||
return Enumerable.Empty<IMedia>();
|
||||
}
|
||||
return GetPagedDescendantsLocked(mediaPath[0].Path, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
return GetPagedLocked(GetPagedDescendantQuery(mediaPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
return GetPagedLocked(GetPagedDescendantQuery(null), pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IMedia> GetPagedDescendantsLocked(string mediaPath, long pageIndex, int pageSize, out long totalChildren,
|
||||
private IQuery<IMedia> GetPagedDescendantQuery(string mediaPath)
|
||||
{
|
||||
var query = Query<IMedia>();
|
||||
if (!mediaPath.IsNullOrWhiteSpace())
|
||||
query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar));
|
||||
return query;
|
||||
}
|
||||
|
||||
private IEnumerable<IMedia> GetPagedLocked(IQuery<IMedia> query, long pageIndex, int pageSize, out long totalChildren,
|
||||
IQuery<IMedia> filter, Ordering ordering)
|
||||
{
|
||||
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
|
||||
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
|
||||
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
|
||||
|
||||
var query = Query<IMedia>();
|
||||
if (!mediaPath.IsNullOrWhiteSpace())
|
||||
query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar));
|
||||
|
||||
return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
|
||||
@@ -888,7 +895,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
public Attempt<OperationResult> MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
var moves = new List<Tuple<IMedia, string>>();
|
||||
var moves = new List<(IMedia, string)>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -940,7 +947,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
var moves = new List<Tuple<IMedia, string>>();
|
||||
var moves = new List<(IMedia, string)>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -979,7 +986,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// MUST be called from within WriteLock
|
||||
// trash indicates whether we are trashing, un-trashing, or not changing anything
|
||||
private void PerformMoveLocked(IMedia media, int parentId, IMedia parent, int userId, ICollection<Tuple<IMedia, string>> moves, bool? trash)
|
||||
private void PerformMoveLocked(IMedia media, int parentId, IMedia parent, int userId, ICollection<(IMedia, string)> moves, bool? trash)
|
||||
{
|
||||
media.ParentId = parentId;
|
||||
|
||||
@@ -989,7 +996,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var paths = new Dictionary<int, string>();
|
||||
|
||||
moves.Add(Tuple.Create(media, media.Path)); // capture original path
|
||||
moves.Add((media, media.Path)); // capture original path
|
||||
|
||||
//need to store the original path to lookup descendants based on it below
|
||||
var originalPath = media.Path;
|
||||
@@ -1006,21 +1013,25 @@ namespace Umbraco.Core.Services.Implement
|
||||
paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : Constants.System.RootString) : parent.Path) + "," + media.Id;
|
||||
|
||||
const int pageSize = 500;
|
||||
var page = 0;
|
||||
var total = long.MaxValue;
|
||||
while (page * pageSize < total)
|
||||
var query = GetPagedDescendantQuery(originalPath);
|
||||
long total;
|
||||
do
|
||||
{
|
||||
var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
|
||||
// We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
|
||||
var descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
|
||||
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path
|
||||
moves.Add((descendant, descendant.Path)); // capture original path
|
||||
|
||||
// update path and level since we do not update parentId
|
||||
descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
|
||||
descendant.Level += levelDelta;
|
||||
PerformMoveMediaLocked(descendant, userId, trash);
|
||||
}
|
||||
}
|
||||
|
||||
} while (total > pageSize);
|
||||
|
||||
}
|
||||
|
||||
private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash)
|
||||
@@ -1132,6 +1143,26 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MediaTree);
|
||||
|
||||
var report = _mediaRepository.CheckDataIntegrity(options);
|
||||
|
||||
if (report.FixedIssues.Count > 0)
|
||||
{
|
||||
//The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
|
||||
var root = new Models.Media("root", -1, new MediaType(_shortStringHelper, -1)) { Id = -1, Key = Guid.Empty };
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IMedia>.EventArgs(new TreeChange<IMedia>(root, TreeChangeTypes.RefreshAll)));
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1270,7 +1301,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
|
||||
|
||||
var changes = new List<TreeChange<IMedia>>();
|
||||
var moves = new List<Tuple<IMedia, string>>();
|
||||
var moves = new List<(IMedia, string)>();
|
||||
var mediaTypeIdsA = mediaTypeIds.ToArray();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
@@ -1351,5 +1382,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,10 @@
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Umbraco.Tests.Common</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Umbraco.Tests.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -505,6 +505,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the <see cref="ContentNodeKit"/> and try to create a parent <see cref="LinkedNode{ContentNode}"/>
|
||||
/// </summary>
|
||||
/// <param name="kit"></param>
|
||||
/// <param name="parent"></param>
|
||||
/// <returns>
|
||||
/// Returns false if the parent was not found or if the kit validation failed
|
||||
/// </returns>
|
||||
private bool BuildKit(ContentNodeKit kit, out LinkedNode<ContentNode> parent)
|
||||
{
|
||||
// make sure parent exists
|
||||
@@ -515,6 +523,15 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
return false;
|
||||
}
|
||||
|
||||
// We cannot continue if there's no value. This shouldn't happen but it can happen if the database umbracoNode.path
|
||||
// data is invalid/corrupt. If that is the case, the parentId might be ok but not the Path which can result in null
|
||||
// because the data sort operation is by path.
|
||||
if (parent.Value == null)
|
||||
{
|
||||
_logger.Warn<ContentStore>($"Skip item id={kit.Node.Id}, no Data assigned for linked node with path {kit.Node.Path} and parent id {kit.Node.ParentContentId}. This can indicate data corruption for the Path value for node {kit.Node.Id}. See the Health Check dashboard in Settings to resolve data integrity issues.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the kit is valid
|
||||
if (kit.DraftData == null && kit.PublishedData == null)
|
||||
{
|
||||
@@ -803,7 +820,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
//this zero's out the branch (recursively), if we're in a new gen this will add a NULL placeholder for the gen
|
||||
ClearBranchLocked(existing);
|
||||
//TODO: This removes the current GEN from the tree - do we really want to do that?
|
||||
//TODO: This removes the current GEN from the tree - do we really want to do that? (not sure if this is still an issue....)
|
||||
RemoveTreeNodeLocked(existing);
|
||||
}
|
||||
|
||||
@@ -868,6 +885,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
private void ClearBranchLocked(ContentNode content)
|
||||
{
|
||||
// This should never be null, all code that calls this method is null checking but we've seen
|
||||
// issues of null ref exceptions in issue reports so we'll double check here
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
|
||||
SetValueLocked(_contentNodes, content.Id, null);
|
||||
if (_localDb != null) RegisterChange(content.Id, ContentNodeKit.Null);
|
||||
|
||||
@@ -876,9 +897,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
var id = content.FirstChildContentId;
|
||||
while (id > 0)
|
||||
{
|
||||
// get the required link node, this ensures that both `link` and `link.Value` are not null
|
||||
var link = GetRequiredLinkedNode(id, "child", null);
|
||||
ClearBranchLocked(link.Value);
|
||||
id = link.Value.NextSiblingContentId;
|
||||
var linkValue = link.Value; // capture local since clearing in recurse can clear it
|
||||
ClearBranchLocked(linkValue); // recurse
|
||||
id = linkValue.NextSiblingContentId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1033,6 +1056,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
var parent = parentLink.Value;
|
||||
|
||||
// We are doing a null check here but this should no longer be possible because we have a null check in BuildKit
|
||||
// for the parent.Value property and we'll output a warning. However I'll leave this additional null check in place.
|
||||
// see https://github.com/umbraco/Umbraco-CMS/issues/7868
|
||||
if (parent == null)
|
||||
throw new PanicException($"A null Value was returned on the {nameof(parentLink)} LinkedNode with id={content.ParentContentId}, potentially your database paths are corrupted.");
|
||||
|
||||
// if parent has no children, clone parent + add as first child
|
||||
if (parent.FirstChildContentId < 0)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{
|
||||
public LinkedNode(TValue value, long gen, LinkedNode<TValue> next = null)
|
||||
{
|
||||
Value = value;
|
||||
Value = value; // This is allowed to be null, we actually explicitly set this to null in ClearLocked
|
||||
Gen = gen;
|
||||
Next = next;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,10 @@ namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
private IDictionary<string, object> _defaultConfiguration;
|
||||
|
||||
|
||||
public ConfigurationEditorBuilder(TParent parentBuilder) : base(parentBuilder)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public ConfigurationEditorBuilder<TParent> WithDefaultConfiguration(IDictionary<string, object> defaultConfiguration)
|
||||
{
|
||||
_defaultConfiguration = defaultConfiguration;
|
||||
@@ -28,6 +26,5 @@ namespace Umbraco.Tests.Common.Builders
|
||||
DefaultConfiguration = defaultConfiguration,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
public class DataEditorBuilder<TParent> : ChildBuilderBase<TParent, IDataEditor>
|
||||
{
|
||||
private readonly ConfigurationEditorBuilder<DataEditorBuilder<TParent>> _explicitConfigurationEditorBuilder;
|
||||
private readonly DataValueEditorBuilder<DataEditorBuilder<TParent>> _explicitValueEditorBuilder;
|
||||
private ConfigurationEditorBuilder<DataEditorBuilder<TParent>> _explicitConfigurationEditorBuilder;
|
||||
private DataValueEditorBuilder<DataEditorBuilder<TParent>> _explicitValueEditorBuilder;
|
||||
private IDictionary<string, object> _defaultConfiguration;
|
||||
|
||||
public DataEditorBuilder(TParent parentBuilder) : base(parentBuilder)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Umbraco.Tests.Common.Builders
|
||||
IWithPathBuilder,
|
||||
IWithSortOrderBuilder
|
||||
{
|
||||
private readonly DataEditorBuilder<DataTypeBuilder> _dataEditorBuilder;
|
||||
private DataEditorBuilder<DataTypeBuilder> _dataEditorBuilder;
|
||||
private int? _id;
|
||||
private int? _parentId;
|
||||
private Guid? _key;
|
||||
@@ -28,7 +28,6 @@ namespace Umbraco.Tests.Common.Builders
|
||||
private DateTime? _deleteDate;
|
||||
private string _name;
|
||||
private bool? _trashed;
|
||||
// private object _configuration;
|
||||
private int? _level;
|
||||
private string _path;
|
||||
private int? _creatorId;
|
||||
@@ -40,12 +39,6 @@ namespace Umbraco.Tests.Common.Builders
|
||||
_dataEditorBuilder = new DataEditorBuilder<DataTypeBuilder>(this);
|
||||
}
|
||||
|
||||
// public DataTypeBuilder WithConfiguration(object configuration)
|
||||
// {
|
||||
// _configuration = configuration;
|
||||
// return this;
|
||||
// }
|
||||
|
||||
public DataTypeBuilder WithDatabaseType(ValueStorageType databaseType)
|
||||
{
|
||||
_databaseType = databaseType;
|
||||
@@ -67,7 +60,6 @@ namespace Umbraco.Tests.Common.Builders
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var deleteDate = _deleteDate ?? null;
|
||||
var name = _name ?? Guid.NewGuid().ToString();
|
||||
// var configuration = _configuration ?? editor.GetConfigurationEditor().DefaultConfigurationObject;
|
||||
var level = _level ?? 0;
|
||||
var path = _path ?? string.Empty;
|
||||
var creatorId = _creatorId ?? 1;
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace Umbraco.Tests.Common.Builders
|
||||
private bool? _hideLabel;
|
||||
private string _valueType;
|
||||
|
||||
|
||||
public DataValueEditorBuilder(TParent parentBuilder) : base(parentBuilder)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@ namespace Umbraco.Tests.Common.Builders
|
||||
IWithDeleteDateBuilder,
|
||||
IWithKeyBuilder
|
||||
{
|
||||
private readonly LanguageBuilder<DictionaryTranslationBuilder> _languageBuilder;
|
||||
private readonly Guid? _uniqueId = null;
|
||||
private LanguageBuilder<DictionaryTranslationBuilder> _languageBuilder;
|
||||
private DateTime? _createDate;
|
||||
private DateTime? _deleteDate;
|
||||
private int? _id;
|
||||
@@ -26,6 +25,36 @@ namespace Umbraco.Tests.Common.Builders
|
||||
_languageBuilder = new LanguageBuilder<DictionaryTranslationBuilder>(this);
|
||||
}
|
||||
|
||||
public LanguageBuilder<DictionaryTranslationBuilder> AddLanguage() => _languageBuilder;
|
||||
|
||||
public DictionaryTranslationBuilder WithValue(string value)
|
||||
{
|
||||
_value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IDictionaryTranslation Build()
|
||||
{
|
||||
var createDate = _createDate ?? DateTime.Now;
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var deleteDate = _deleteDate ?? null;
|
||||
var id = _id ?? 1;
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
|
||||
var result = new DictionaryTranslation(
|
||||
_languageBuilder.Build(),
|
||||
_value ?? Guid.NewGuid().ToString(),
|
||||
key)
|
||||
{
|
||||
CreateDate = createDate,
|
||||
UpdateDate = updateDate,
|
||||
DeleteDate = deleteDate,
|
||||
Id = id
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DateTime? IWithCreateDateBuilder.CreateDate
|
||||
{
|
||||
get => _createDate;
|
||||
@@ -55,35 +84,5 @@ namespace Umbraco.Tests.Common.Builders
|
||||
get => _updateDate;
|
||||
set => _updateDate = value;
|
||||
}
|
||||
|
||||
public override IDictionaryTranslation Build()
|
||||
{
|
||||
var createDate = _createDate ?? DateTime.Now;
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var deleteDate = _deleteDate ?? null;
|
||||
var id = _id ?? 1;
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
|
||||
var result = new DictionaryTranslation(
|
||||
_languageBuilder.Build(),
|
||||
_value ?? Guid.NewGuid().ToString(),
|
||||
_uniqueId ?? key)
|
||||
{
|
||||
CreateDate = createDate,
|
||||
UpdateDate = updateDate,
|
||||
DeleteDate = deleteDate,
|
||||
Id = id
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public LanguageBuilder<DictionaryTranslationBuilder> AddLanguage() => _languageBuilder;
|
||||
|
||||
public DictionaryTranslationBuilder WithValue(string value)
|
||||
{
|
||||
_value = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Tests.Common.Builders.Interfaces;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
public class EntitySlimBuilder
|
||||
: BuilderBase<EntitySlim>,
|
||||
IWithIdBuilder,
|
||||
IWithParentIdBuilder
|
||||
{
|
||||
private int? _id;
|
||||
private int? _parentId;
|
||||
|
||||
public override EntitySlim Build()
|
||||
{
|
||||
var id = _id ?? 1;
|
||||
var parentId = _parentId ?? -1;
|
||||
|
||||
return new EntitySlim
|
||||
{
|
||||
Id = id,
|
||||
ParentId = parentId,
|
||||
};
|
||||
}
|
||||
|
||||
public EntitySlimBuilder WithNoParentId()
|
||||
{
|
||||
_parentId = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
int? IWithIdBuilder.Id
|
||||
{
|
||||
get => _id;
|
||||
set => _id = value;
|
||||
}
|
||||
|
||||
int? IWithParentIdBuilder.ParentId
|
||||
{
|
||||
get => _parentId;
|
||||
set => _parentId = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,13 @@ namespace Umbraco.Tests.Common.Builders.Extensions
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithoutIdentity<T>(this T builder)
|
||||
where T : IWithIdBuilder
|
||||
{
|
||||
builder.Id = 0;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithCreatorId<T>(this T builder, int creatorId)
|
||||
where T : IWithCreatorIdBuilder
|
||||
{
|
||||
@@ -116,5 +123,60 @@ namespace Umbraco.Tests.Common.Builders.Extensions
|
||||
builder.Thumbnail = thumbnail;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithLogin<T>(this T builder, string username, string rawPasswordValue)
|
||||
where T : IWithLoginBuilder
|
||||
{
|
||||
builder.Username = username;
|
||||
builder.RawPasswordValue = rawPasswordValue;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithEmail<T>(this T builder, string email)
|
||||
where T : IWithEmailBuilder
|
||||
{
|
||||
builder.Email = email;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithFailedPasswordAttempts<T>(this T builder, int failedPasswordAttempts)
|
||||
where T : IWithFailedPasswordAttemptsBuilder
|
||||
{
|
||||
builder.FailedPasswordAttempts = failedPasswordAttempts;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithIsApproved<T>(this T builder, bool isApproved)
|
||||
where T : IWithIsApprovedBuilder
|
||||
{
|
||||
builder.IsApproved = isApproved;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithIsLockedOut<T>(this T builder, bool isLockedOut, DateTime? lastLockoutDate = null)
|
||||
where T : IWithIsLockedOutBuilder
|
||||
{
|
||||
builder.IsLockedOut = isLockedOut;
|
||||
if (lastLockoutDate.HasValue)
|
||||
{
|
||||
builder.LastLockoutDate = lastLockoutDate.Value;
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithLastLoginDate<T>(this T builder, DateTime lastLoginDate)
|
||||
where T : IWithLastLoginDateBuilder
|
||||
{
|
||||
builder.LastLoginDate = lastLoginDate;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithLastPasswordChangeDate<T>(this T builder, DateTime lastPasswordChangeDate)
|
||||
where T : IWithLastPasswordChangeDateBuilder
|
||||
{
|
||||
builder.LastPasswordChangeDate = lastPasswordChangeDate;
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
@@ -14,7 +15,8 @@ namespace Umbraco.Tests.Common.Builders
|
||||
|
||||
public override IEnumerable<T> Build()
|
||||
{
|
||||
return _collection;
|
||||
var collection = _collection?.ToList() ?? Enumerable.Empty<T>();
|
||||
return collection;
|
||||
}
|
||||
|
||||
public GenericCollectionBuilder<TBuilder, T> WithValue(T value)
|
||||
|
||||
@@ -14,7 +14,9 @@ namespace Umbraco.Tests.Common.Builders
|
||||
|
||||
public override IDictionary<TKey, TValue> Build()
|
||||
{
|
||||
return _dictionary;
|
||||
return _dictionary == null
|
||||
? new Dictionary<TKey, TValue>()
|
||||
: new Dictionary<TKey, TValue>(_dictionary);
|
||||
}
|
||||
|
||||
public GenericDictionaryBuilder<TBuilder, TKey, TValue> WithKeyValue(TKey key, TValue value)
|
||||
|
||||
@@ -34,7 +34,6 @@ namespace Umbraco.Tests.Common.Builders
|
||||
private int? _versionCheckPeriod;
|
||||
private readonly SmtpSettingsBuilder<GlobalSettingsBuilder<TParent>> _smtpSettingsBuilder;
|
||||
|
||||
|
||||
public GlobalSettingsBuilder(TParent parentBuilder) : base(parentBuilder)
|
||||
{
|
||||
_smtpSettingsBuilder = new SmtpSettingsBuilder<GlobalSettingsBuilder<TParent>>(this);
|
||||
@@ -192,7 +191,6 @@ namespace Umbraco.Tests.Common.Builders
|
||||
var mainDomLock = _mainDomLock ?? string.Empty;
|
||||
var noNodesViewPath = _noNodesViewPath ?? "~/config/splashes/NoNodes.cshtml";
|
||||
|
||||
|
||||
return new TestGlobalSettings
|
||||
{
|
||||
ConfigurationStatus = configurationStatus,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IAccountBuilder : IWithLoginBuilder,
|
||||
IWithEmailBuilder,
|
||||
IWithFailedPasswordAttemptsBuilder,
|
||||
IWithIsApprovedBuilder,
|
||||
IWithIsLockedOutBuilder,
|
||||
IWithLastLoginDateBuilder,
|
||||
IWithLastPasswordChangeDateBuilder
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithApprovedBuilder
|
||||
{
|
||||
bool? Approved { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithEmailBuilder
|
||||
{
|
||||
string Email { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithFailedPasswordAttemptsBuilder
|
||||
{
|
||||
int? FailedPasswordAttempts { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithIsApprovedBuilder
|
||||
{
|
||||
bool? IsApproved { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithIsLockedOutBuilder
|
||||
{
|
||||
bool? IsLockedOut { get; set; }
|
||||
|
||||
DateTime? LastLockoutDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithLastLoginDateBuilder
|
||||
{
|
||||
DateTime? LastLoginDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithLastPasswordChangeDateBuilder
|
||||
{
|
||||
DateTime? LastPasswordChangeDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithLoginBuilder
|
||||
{
|
||||
string Username { get; set; }
|
||||
|
||||
string RawPasswordValue { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,50 @@ namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
}
|
||||
|
||||
public LanguageBuilder<TParent> WithIsDefault(bool isDefault)
|
||||
{
|
||||
_isDefault = isDefault;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LanguageBuilder<TParent> WithIsMandatory(bool isMandatory)
|
||||
{
|
||||
_isMandatory = isMandatory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LanguageBuilder<TParent> WithFallbackLanguageId(int fallbackLanguageId)
|
||||
{
|
||||
_fallbackLanguageId = fallbackLanguageId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override ILanguage Build()
|
||||
{
|
||||
var cultureInfo = _cultureInfo ?? CultureInfo.GetCultureInfo("en-US");
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
var createDate = _createDate ?? DateTime.Now;
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var deleteDate = _deleteDate ?? null;
|
||||
var fallbackLanguageId = _fallbackLanguageId ?? null;
|
||||
var isDefault = _isDefault ?? false;
|
||||
var isMandatory = _isMandatory ?? false;
|
||||
|
||||
return new Language(Mock.Of<IGlobalSettings>(), cultureInfo.Name)
|
||||
{
|
||||
Id = _id ?? 1,
|
||||
CultureName = cultureInfo.TwoLetterISOLanguageName,
|
||||
IsoCode = new RegionInfo(cultureInfo.LCID).Name,
|
||||
Key = key,
|
||||
CreateDate = createDate,
|
||||
UpdateDate = updateDate,
|
||||
DeleteDate = deleteDate,
|
||||
IsDefault = isDefault,
|
||||
IsMandatory = isMandatory,
|
||||
FallbackLanguageId = fallbackLanguageId
|
||||
};
|
||||
}
|
||||
|
||||
DateTime? IWithCreateDateBuilder.CreateDate
|
||||
{
|
||||
get => _createDate;
|
||||
@@ -72,49 +116,5 @@ namespace Umbraco.Tests.Common.Builders
|
||||
get => _updateDate;
|
||||
set => _updateDate = value;
|
||||
}
|
||||
|
||||
public override ILanguage Build()
|
||||
{
|
||||
var cultureInfo = _cultureInfo ?? CultureInfo.GetCultureInfo("en-US");
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
var createDate = _createDate ?? DateTime.Now;
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var deleteDate = _deleteDate ?? null;
|
||||
var fallbackLanguageId = _fallbackLanguageId ?? null;
|
||||
var isDefault = _isDefault ?? false;
|
||||
var isMandatory = _isMandatory ?? false;
|
||||
|
||||
return new Language(Mock.Of<IGlobalSettings>(), cultureInfo.Name)
|
||||
{
|
||||
Id = _id ?? 1,
|
||||
CultureName = cultureInfo.TwoLetterISOLanguageName,
|
||||
IsoCode = new RegionInfo(cultureInfo.LCID).Name,
|
||||
Key = key,
|
||||
CreateDate = createDate,
|
||||
UpdateDate = updateDate,
|
||||
DeleteDate = deleteDate,
|
||||
IsDefault = isDefault,
|
||||
IsMandatory = isMandatory,
|
||||
FallbackLanguageId = fallbackLanguageId
|
||||
};
|
||||
}
|
||||
|
||||
public LanguageBuilder<TParent> WithIsDefault(bool isDefault)
|
||||
{
|
||||
_isDefault = isDefault;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LanguageBuilder<TParent> WithIsMandatory(bool isMandatory)
|
||||
{
|
||||
_isMandatory = isMandatory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LanguageBuilder<TParent> WithFallbackLanguageId(int fallbackLanguageId)
|
||||
{
|
||||
_fallbackLanguageId = fallbackLanguageId;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Tests.Common.Builders.Interfaces;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
public class MacroBuilder
|
||||
: BuilderBase<Macro>,
|
||||
IWithIdBuilder,
|
||||
IWithKeyBuilder,
|
||||
IWithAliasBuilder,
|
||||
IWithNameBuilder
|
||||
{
|
||||
private List<MacroPropertyBuilder> _propertyBuilders = new List<MacroPropertyBuilder>();
|
||||
|
||||
private int? _id;
|
||||
private Guid? _key;
|
||||
private string _alias;
|
||||
private string _name;
|
||||
private bool? _useInEditor;
|
||||
private int? _cacheDuration;
|
||||
private bool? _cacheByPage;
|
||||
private bool? _cacheByMember;
|
||||
private bool? _dontRender;
|
||||
private string _macroSource;
|
||||
|
||||
public MacroBuilder WithUseInEditor(bool useInEditor)
|
||||
{
|
||||
_useInEditor = useInEditor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MacroBuilder WithCacheDuration(int cacheDuration)
|
||||
{
|
||||
_cacheDuration = cacheDuration;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MacroBuilder WithCacheByPage(bool cacheByPage)
|
||||
{
|
||||
_cacheByPage = cacheByPage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MacroBuilder WithCacheByMember(bool cacheByMember)
|
||||
{
|
||||
_cacheByMember = cacheByMember;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MacroBuilder WithDontRender(bool dontRender)
|
||||
{
|
||||
_dontRender = dontRender;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MacroBuilder WithSource(string macroSource)
|
||||
{
|
||||
_macroSource = macroSource;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MacroPropertyBuilder AddProperty()
|
||||
{
|
||||
var builder = new MacroPropertyBuilder(this);
|
||||
|
||||
_propertyBuilders.Add(builder);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public override Macro Build()
|
||||
{
|
||||
var id = _id ?? 1;
|
||||
var name = _name ?? Guid.NewGuid().ToString();
|
||||
var alias = _alias ?? name.ToCamelCase();
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
var useInEditor = _useInEditor ?? false;
|
||||
var cacheDuration = _cacheDuration ?? 0;
|
||||
var cacheByPage = _cacheByPage ?? false;
|
||||
var cacheByMember = _cacheByMember ?? false;
|
||||
var dontRender = _dontRender ?? false;
|
||||
var macroSource = _macroSource ?? string.Empty;
|
||||
|
||||
var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig());
|
||||
|
||||
var macro = new Macro(shortStringHelper, id, key, useInEditor, cacheDuration, alias, name, cacheByPage, cacheByMember, dontRender, macroSource);
|
||||
|
||||
foreach (var property in _propertyBuilders.Select(x => x.Build()))
|
||||
{
|
||||
macro.Properties.Add(property);
|
||||
}
|
||||
|
||||
return macro;
|
||||
}
|
||||
|
||||
int? IWithIdBuilder.Id
|
||||
{
|
||||
get => _id;
|
||||
set => _id = value;
|
||||
}
|
||||
|
||||
Guid? IWithKeyBuilder.Key
|
||||
{
|
||||
get => _key;
|
||||
set => _key = value;
|
||||
}
|
||||
|
||||
string IWithAliasBuilder.Alias
|
||||
{
|
||||
get => _alias;
|
||||
set => _alias = value;
|
||||
}
|
||||
|
||||
string IWithNameBuilder.Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Tests.Common.Builders.Interfaces;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
public class MacroPropertyBuilder
|
||||
: ChildBuilderBase<MacroBuilder, IMacroProperty>,
|
||||
IWithIdBuilder,
|
||||
IWithKeyBuilder,
|
||||
IWithAliasBuilder,
|
||||
IWithNameBuilder,
|
||||
IWithSortOrderBuilder
|
||||
{
|
||||
private int? _id;
|
||||
private Guid? _key;
|
||||
private string _alias;
|
||||
private string _name;
|
||||
private int? _sortOrder;
|
||||
private string _editorAlias;
|
||||
|
||||
public MacroPropertyBuilder(MacroBuilder parentBuilder) : base(parentBuilder)
|
||||
{
|
||||
}
|
||||
|
||||
public MacroPropertyBuilder WithEditorAlias(string editorAlias)
|
||||
{
|
||||
_editorAlias = editorAlias;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IMacroProperty Build()
|
||||
{
|
||||
var id = _id ?? 1;
|
||||
var name = _name ?? Guid.NewGuid().ToString();
|
||||
var alias = _alias ?? name.ToCamelCase();
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
var sortOrder = _sortOrder ?? 0;
|
||||
var editorAlias = _editorAlias ?? string.Empty;
|
||||
|
||||
return new MacroProperty(id, key, alias, name, sortOrder, editorAlias);
|
||||
}
|
||||
|
||||
int? IWithIdBuilder.Id
|
||||
{
|
||||
get => _id;
|
||||
set => _id = value;
|
||||
}
|
||||
|
||||
Guid? IWithKeyBuilder.Key
|
||||
{
|
||||
get => _key;
|
||||
set => _key = value;
|
||||
}
|
||||
|
||||
string IWithAliasBuilder.Alias
|
||||
{
|
||||
get => _alias;
|
||||
set => _alias = value;
|
||||
}
|
||||
|
||||
string IWithNameBuilder.Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value;
|
||||
}
|
||||
|
||||
int? IWithSortOrderBuilder.SortOrder
|
||||
{
|
||||
get => _sortOrder;
|
||||
set => _sortOrder = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ namespace Umbraco.Tests.Common.Builders
|
||||
IWithTrashedBuilder,
|
||||
IWithLevelBuilder,
|
||||
IWithPathBuilder,
|
||||
IWithSortOrderBuilder
|
||||
IWithSortOrderBuilder,
|
||||
IAccountBuilder
|
||||
{
|
||||
private MemberTypeBuilder _memberTypeBuilder;
|
||||
private GenericCollectionBuilder<MemberBuilder, string> _memberGroupsBuilder;
|
||||
@@ -28,12 +29,12 @@ namespace Umbraco.Tests.Common.Builders
|
||||
private DateTime? _updateDate;
|
||||
private string _name;
|
||||
private int? _creatorId;
|
||||
private int? _level;
|
||||
private string _path;
|
||||
private string _username;
|
||||
private string _rawPasswordValue;
|
||||
private string _email;
|
||||
private int? _failedPasswordAttempts;
|
||||
private int? _level;
|
||||
private string _path;
|
||||
private bool? _isApproved;
|
||||
private bool? _isLockedOut;
|
||||
private DateTime? _lastLockoutDate;
|
||||
@@ -43,60 +44,6 @@ namespace Umbraco.Tests.Common.Builders
|
||||
private bool? _trashed;
|
||||
private int? _propertyIdsIncrementingFrom;
|
||||
|
||||
public MemberBuilder WithUserName(string username)
|
||||
{
|
||||
_username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithEmail(string email)
|
||||
{
|
||||
_email = email;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithRawPasswordValue(string rawPasswordValue)
|
||||
{
|
||||
_rawPasswordValue = rawPasswordValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithFailedPasswordAttempts(int failedPasswordAttempts)
|
||||
{
|
||||
_failedPasswordAttempts = failedPasswordAttempts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithIsApproved(bool isApproved)
|
||||
{
|
||||
_isApproved = isApproved;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithIsLockedOut(bool isLockedOut)
|
||||
{
|
||||
_isLockedOut = isLockedOut;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithLastLockoutDate(DateTime lastLockoutDate)
|
||||
{
|
||||
_lastLockoutDate = lastLockoutDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithLastLoginDate(DateTime lastLoginDate)
|
||||
{
|
||||
_lastLoginDate = lastLoginDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithLastPasswordChangeDate(DateTime lastPasswordChangeDate)
|
||||
{
|
||||
_lastPasswordChangeDate = lastPasswordChangeDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemberBuilder WithPropertyIdsIncrementingFrom(int propertyIdsIncrementingFrom)
|
||||
{
|
||||
_propertyIdsIncrementingFrom = propertyIdsIncrementingFrom;
|
||||
@@ -139,19 +86,19 @@ namespace Umbraco.Tests.Common.Builders
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var name = _name ?? Guid.NewGuid().ToString();
|
||||
var creatorId = _creatorId ?? 1;
|
||||
var level = _level ?? 1;
|
||||
var path = _path ?? "-1";
|
||||
var sortOrder = _sortOrder ?? 0;
|
||||
var trashed = _trashed ?? false;
|
||||
var username = _username ?? string.Empty;
|
||||
var email = _email ?? string.Empty;
|
||||
var rawPasswordValue = _rawPasswordValue ?? string.Empty;
|
||||
var failedPasswordAttempts = _failedPasswordAttempts ?? 0;
|
||||
var level = _level ?? 1;
|
||||
var path = _path ?? "-1";
|
||||
var isApproved = _isApproved ?? false;
|
||||
var isLockedOut = _isLockedOut ?? false;
|
||||
var lastLockoutDate = _lastLockoutDate ?? DateTime.Now;
|
||||
var lastLoginDate = _lastLoginDate ?? DateTime.Now;
|
||||
var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now;
|
||||
var sortOrder = _sortOrder ?? 0;
|
||||
var trashed = _trashed ?? false;
|
||||
|
||||
if (_memberTypeBuilder == null)
|
||||
{
|
||||
@@ -276,5 +223,59 @@ namespace Umbraco.Tests.Common.Builders
|
||||
get => _sortOrder;
|
||||
set => _sortOrder = value;
|
||||
}
|
||||
|
||||
string IWithLoginBuilder.Username
|
||||
{
|
||||
get => _username;
|
||||
set => _username = value;
|
||||
}
|
||||
|
||||
string IWithLoginBuilder.RawPasswordValue
|
||||
{
|
||||
get => _rawPasswordValue;
|
||||
set => _rawPasswordValue = value;
|
||||
}
|
||||
|
||||
string IWithEmailBuilder.Email
|
||||
{
|
||||
get => _email;
|
||||
set => _email = value;
|
||||
}
|
||||
|
||||
int? IWithFailedPasswordAttemptsBuilder.FailedPasswordAttempts
|
||||
{
|
||||
get => _failedPasswordAttempts;
|
||||
set => _failedPasswordAttempts = value;
|
||||
}
|
||||
|
||||
bool? IWithIsApprovedBuilder.IsApproved
|
||||
{
|
||||
get => _isApproved;
|
||||
set => _isApproved = value;
|
||||
}
|
||||
|
||||
bool? IWithIsLockedOutBuilder.IsLockedOut
|
||||
{
|
||||
get => _isLockedOut;
|
||||
set => _isLockedOut = value;
|
||||
}
|
||||
|
||||
DateTime? IWithIsLockedOutBuilder.LastLockoutDate
|
||||
{
|
||||
get => _lastLockoutDate;
|
||||
set => _lastLockoutDate = value;
|
||||
}
|
||||
|
||||
DateTime? IWithLastLoginDateBuilder.LastLoginDate
|
||||
{
|
||||
get => _lastLoginDate;
|
||||
set => _lastLoginDate = value;
|
||||
}
|
||||
|
||||
DateTime? IWithLastPasswordChangeDateBuilder.LastPasswordChangeDate
|
||||
{
|
||||
get => _lastPasswordChangeDate;
|
||||
set => _lastPasswordChangeDate = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Umbraco.Tests.Common.Builders
|
||||
ValidationRegExpMessage = validationRegExpMessage,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
int? IWithIdBuilder.Id
|
||||
{
|
||||
get => _id;
|
||||
|
||||
@@ -33,6 +33,47 @@ namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
}
|
||||
|
||||
public RelationTypeBuilder WithIsBidirectional(bool isBidirectional)
|
||||
{
|
||||
_isBidirectional = isBidirectional;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationTypeBuilder WithChildObjectType(Guid childObjectType)
|
||||
{
|
||||
_childObjectType = childObjectType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationTypeBuilder WithParentObjectType(Guid parentObjectType)
|
||||
{
|
||||
_parentObjectType = parentObjectType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IRelationType Build()
|
||||
{
|
||||
var alias = _alias ?? Guid.NewGuid().ToString();
|
||||
var name = _name ?? Guid.NewGuid().ToString();
|
||||
var parentObjectType = _parentObjectType ?? null;
|
||||
var childObjectType = _childObjectType ?? null;
|
||||
var id = _id ?? 1;
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
var isBidirectional = _isBidirectional ?? false;
|
||||
var createDate = _createDate ?? DateTime.Now;
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var deleteDate = _deleteDate ?? null;
|
||||
|
||||
return new RelationType(name, alias, isBidirectional, parentObjectType, childObjectType)
|
||||
{
|
||||
Id = id,
|
||||
Key = key,
|
||||
CreateDate = createDate,
|
||||
UpdateDate = updateDate,
|
||||
DeleteDate = deleteDate
|
||||
};
|
||||
}
|
||||
|
||||
string IWithAliasBuilder.Alias
|
||||
{
|
||||
get => _alias;
|
||||
@@ -74,46 +115,5 @@ namespace Umbraco.Tests.Common.Builders
|
||||
get => _updateDate;
|
||||
set => _updateDate = value;
|
||||
}
|
||||
|
||||
public override IRelationType Build()
|
||||
{
|
||||
var alias = _alias ?? Guid.NewGuid().ToString();
|
||||
var name = _name ?? Guid.NewGuid().ToString();
|
||||
var parentObjectType = _parentObjectType ?? null;
|
||||
var childObjectType = _childObjectType ?? null;
|
||||
var id = _id ?? 1;
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
var isBidirectional = _isBidirectional ?? false;
|
||||
var createDate = _createDate ?? DateTime.Now;
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var deleteDate = _deleteDate ?? null;
|
||||
|
||||
return new RelationType(name, alias, isBidirectional, parentObjectType, childObjectType)
|
||||
{
|
||||
Id = id,
|
||||
Key = key,
|
||||
CreateDate = createDate,
|
||||
UpdateDate = updateDate,
|
||||
DeleteDate = deleteDate
|
||||
};
|
||||
}
|
||||
|
||||
public RelationTypeBuilder WithIsBidirectional(bool isBidirectional)
|
||||
{
|
||||
_isBidirectional = isBidirectional;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationTypeBuilder WithChildObjectType(Guid childObjectType)
|
||||
{
|
||||
_childObjectType = childObjectType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RelationTypeBuilder WithParentObjectType(Guid parentObjectType)
|
||||
{
|
||||
_parentObjectType = parentObjectType;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,65 +9,64 @@ namespace Umbraco.Tests.Common.Builders
|
||||
}
|
||||
}
|
||||
|
||||
public class SmtpSettingsBuilder<TParent>
|
||||
: ChildBuilderBase<TParent, ISmtpSettings>
|
||||
{
|
||||
private string _from;
|
||||
private string _host;
|
||||
private int? _port;
|
||||
private string _pickupDirectoryLocation;
|
||||
public class SmtpSettingsBuilder<TParent>
|
||||
: ChildBuilderBase<TParent, ISmtpSettings>
|
||||
{
|
||||
private string _from;
|
||||
private string _host;
|
||||
private int? _port;
|
||||
private string _pickupDirectoryLocation;
|
||||
|
||||
public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder)
|
||||
{
|
||||
}
|
||||
public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder)
|
||||
{
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithFrom(string from)
|
||||
{
|
||||
_from = from;
|
||||
return this;
|
||||
}
|
||||
public SmtpSettingsBuilder<TParent> WithFrom(string from)
|
||||
{
|
||||
_from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithHost(string host)
|
||||
{
|
||||
_host = host;
|
||||
return this;
|
||||
}
|
||||
public SmtpSettingsBuilder<TParent> WithHost(string host)
|
||||
{
|
||||
_host = host;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithPost(int port)
|
||||
{
|
||||
_port = port;
|
||||
return this;
|
||||
}
|
||||
public SmtpSettingsBuilder<TParent> WithPost(int port)
|
||||
{
|
||||
_port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithPickupDirectoryLocation(string pickupDirectoryLocation)
|
||||
{
|
||||
_pickupDirectoryLocation = pickupDirectoryLocation;
|
||||
return this;
|
||||
}
|
||||
public SmtpSettingsBuilder<TParent> WithPickupDirectoryLocation(string pickupDirectoryLocation)
|
||||
{
|
||||
_pickupDirectoryLocation = pickupDirectoryLocation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override ISmtpSettings Build()
|
||||
{
|
||||
var from = _from ?? null;
|
||||
var host = _host ?? null;
|
||||
var port = _port ?? 25;
|
||||
var pickupDirectoryLocation = _pickupDirectoryLocation ?? null;
|
||||
|
||||
public override ISmtpSettings Build()
|
||||
{
|
||||
var from = _from ?? null;
|
||||
var host = _host ?? null;
|
||||
var port = _port ?? 25;
|
||||
var pickupDirectoryLocation = _pickupDirectoryLocation ?? null;
|
||||
|
||||
return new TestSmtpSettings()
|
||||
{
|
||||
return new TestSmtpSettings()
|
||||
{
|
||||
From = from,
|
||||
Host = host,
|
||||
Port = port,
|
||||
PickupDirectoryLocation = pickupDirectoryLocation,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class TestSmtpSettings : ISmtpSettings
|
||||
{
|
||||
public string From { get; set; }
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string PickupDirectoryLocation { get; set; }
|
||||
}
|
||||
}
|
||||
private class TestSmtpSettings : ISmtpSettings
|
||||
{
|
||||
public string From { get; set; }
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string PickupDirectoryLocation { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace Umbraco.Tests.Common.Builders
|
||||
var masterTemplateId = _masterTemplateId ?? null;
|
||||
|
||||
var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig());
|
||||
|
||||
return new Template(shortStringHelper, name, alias)
|
||||
{
|
||||
Id = id,
|
||||
|
||||
@@ -1,41 +1,69 @@
|
||||
using Umbraco.Configuration.Models;
|
||||
using System;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Tests.Common.Builders.Interfaces;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
|
||||
public class UserBuilder : UserBuilder<object>
|
||||
{
|
||||
public UserBuilder() : base(null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UserBuilder<TParent>
|
||||
: ChildBuilderBase<TParent, User>,
|
||||
IWithIdBuilder,
|
||||
IWithKeyBuilder,
|
||||
IWithCreateDateBuilder,
|
||||
IWithUpdateDateBuilder,
|
||||
IWithNameBuilder,
|
||||
IWithApprovedBuilder
|
||||
IAccountBuilder
|
||||
{
|
||||
private int? _id;
|
||||
private Guid? _key;
|
||||
private DateTime? _createDate;
|
||||
private DateTime? _updateDate;
|
||||
private string _language;
|
||||
private bool? _approved;
|
||||
private string _name;
|
||||
private string _rawPassword;
|
||||
private bool? _isLockedOut;
|
||||
private string _email;
|
||||
private string _username;
|
||||
private string _rawPasswordValue;
|
||||
private string _email;
|
||||
private int? _failedPasswordAttempts;
|
||||
private bool? _isApproved;
|
||||
private bool? _isLockedOut;
|
||||
private DateTime? _lastLockoutDate;
|
||||
private DateTime? _lastLoginDate;
|
||||
private DateTime? _lastPasswordChangeDate;
|
||||
private string _suffix = string.Empty;
|
||||
private string _defaultLang;
|
||||
|
||||
private string _comments;
|
||||
private int? _sessionTimeout;
|
||||
private int[] _startContentIds;
|
||||
private int[] _startMediaIds;
|
||||
|
||||
public UserBuilder(TParent parentBuilder) : base(parentBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public UserBuilder<TParent> WithDefaultUILanguage(string defaultLang)
|
||||
Guid? IWithKeyBuilder.Key
|
||||
{
|
||||
get => _key;
|
||||
set => _key = value;
|
||||
}
|
||||
|
||||
DateTime? IWithCreateDateBuilder.CreateDate
|
||||
{
|
||||
get => _createDate;
|
||||
set => _createDate = value;
|
||||
}
|
||||
|
||||
DateTime? IWithUpdateDateBuilder.UpdateDate
|
||||
{
|
||||
get => _updateDate;
|
||||
set => _updateDate = value;
|
||||
}
|
||||
|
||||
public UserBuilder<TParent> WithDefaultUILanguage(string defaultLang)
|
||||
{
|
||||
_defaultLang = defaultLang;
|
||||
return this;
|
||||
@@ -47,27 +75,27 @@ namespace Umbraco.Tests.Common.Builders
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserBuilder<TParent> WithRawPassword(string rawPassword)
|
||||
public UserBuilder<TParent> WithComments(string comments)
|
||||
{
|
||||
_rawPassword = rawPassword;
|
||||
_comments = comments;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserBuilder<TParent> WithEmail(string email)
|
||||
public UserBuilder<TParent> WithSessionTimeout(int sessionTimeout)
|
||||
{
|
||||
_email = email;
|
||||
_sessionTimeout = sessionTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserBuilder<TParent> WithUsername(string username)
|
||||
public UserBuilder<TParent> WithStartContentIds(int[] startContentIds)
|
||||
{
|
||||
_username = username;
|
||||
_startContentIds = startContentIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public UserBuilder<TParent> WithLockedOut(bool isLockedOut)
|
||||
public UserBuilder<TParent> WithStartMediaIds(int[] startMediaIds)
|
||||
{
|
||||
_isLockedOut = isLockedOut;
|
||||
_startMediaIds = startMediaIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -84,25 +112,50 @@ namespace Umbraco.Tests.Common.Builders
|
||||
|
||||
public override User Build()
|
||||
{
|
||||
var globalSettings = new GlobalSettingsBuilder().WithDefaultUiLanguage(_defaultLang).Build();
|
||||
var id = _id ?? 1;
|
||||
var defaultLang = _defaultLang ?? "en";
|
||||
var globalSettings = new GlobalSettingsBuilder().WithDefaultUiLanguage(defaultLang).Build();
|
||||
var key = _key ?? Guid.NewGuid();
|
||||
var createDate = _createDate ?? DateTime.Now;
|
||||
var updateDate = _updateDate ?? DateTime.Now;
|
||||
var name = _name ?? "TestUser" + _suffix;
|
||||
var email = _email ?? "test" + _suffix + "@test.com";
|
||||
var username = _username ?? "TestUser" + _suffix;
|
||||
var rawPassword = _rawPassword ?? "abcdefghijklmnopqrstuvwxyz";
|
||||
var language = _language ?? globalSettings.DefaultUILanguage;
|
||||
var username = _username ?? "TestUser" + _suffix;
|
||||
var email = _email ?? "test" + _suffix + "@test.com";
|
||||
var rawPasswordValue = _rawPasswordValue ?? "abcdefghijklmnopqrstuvwxyz";
|
||||
var failedPasswordAttempts = _failedPasswordAttempts ?? 0;
|
||||
var isApproved = _isApproved ?? false;
|
||||
var isLockedOut = _isLockedOut ?? false;
|
||||
var approved = _approved ?? true;
|
||||
var lastLockoutDate = _lastLockoutDate ?? DateTime.Now;
|
||||
var lastLoginDate = _lastLoginDate ?? DateTime.Now;
|
||||
var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now;
|
||||
var comments = _comments ?? string.Empty;
|
||||
var sessionTimeout = _sessionTimeout ?? 0;
|
||||
var startContentIds = _startContentIds ?? new int[0];
|
||||
var startMediaIds = _startMediaIds ?? new int[0];
|
||||
|
||||
return new User(
|
||||
globalSettings,
|
||||
name,
|
||||
email,
|
||||
username,
|
||||
rawPassword)
|
||||
rawPasswordValue)
|
||||
{
|
||||
Id = id,
|
||||
Key = key,
|
||||
CreateDate = createDate,
|
||||
UpdateDate = updateDate,
|
||||
Language = language,
|
||||
FailedPasswordAttempts = failedPasswordAttempts,
|
||||
IsApproved = isApproved,
|
||||
IsLockedOut = isLockedOut,
|
||||
IsApproved = approved
|
||||
LastLockoutDate = lastLockoutDate,
|
||||
LastLoginDate = lastLoginDate,
|
||||
LastPasswordChangeDate = lastPasswordChangeDate,
|
||||
Comments = comments,
|
||||
SessionTimeout = sessionTimeout,
|
||||
StartContentIds = startContentIds,
|
||||
StartMediaIds = startMediaIds,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,10 +171,58 @@ namespace Umbraco.Tests.Common.Builders
|
||||
set => _name = value;
|
||||
}
|
||||
|
||||
bool? IWithApprovedBuilder.Approved
|
||||
string IWithLoginBuilder.Username
|
||||
{
|
||||
get => _approved;
|
||||
set => _approved = value;
|
||||
get => _username;
|
||||
set => _username = value;
|
||||
}
|
||||
|
||||
string IWithLoginBuilder.RawPasswordValue
|
||||
{
|
||||
get => _rawPasswordValue;
|
||||
set => _rawPasswordValue = value;
|
||||
}
|
||||
|
||||
string IWithEmailBuilder.Email
|
||||
{
|
||||
get => _email;
|
||||
set => _email = value;
|
||||
}
|
||||
|
||||
int? IWithFailedPasswordAttemptsBuilder.FailedPasswordAttempts
|
||||
{
|
||||
get => _failedPasswordAttempts;
|
||||
set => _failedPasswordAttempts = value;
|
||||
}
|
||||
|
||||
bool? IWithIsApprovedBuilder.IsApproved
|
||||
{
|
||||
get => _isApproved;
|
||||
set => _isApproved = value;
|
||||
}
|
||||
|
||||
bool? IWithIsLockedOutBuilder.IsLockedOut
|
||||
{
|
||||
get => _isLockedOut;
|
||||
set => _isLockedOut = value;
|
||||
}
|
||||
|
||||
DateTime? IWithIsLockedOutBuilder.LastLockoutDate
|
||||
{
|
||||
get => _lastLockoutDate;
|
||||
set => _lastLockoutDate = value;
|
||||
}
|
||||
|
||||
DateTime? IWithLastLoginDateBuilder.LastLoginDate
|
||||
{
|
||||
get => _lastLoginDate;
|
||||
set => _lastLoginDate = value;
|
||||
}
|
||||
|
||||
DateTime? IWithLastPasswordChangeDateBuilder.LastPasswordChangeDate
|
||||
{
|
||||
get => _lastPasswordChangeDate;
|
||||
set => _lastPasswordChangeDate = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using Umbraco.Tests.Common.Builders.Interfaces;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
|
||||
public class UserGroupBuilder : UserGroupBuilder<object>
|
||||
{
|
||||
public UserGroupBuilder() : base(null)
|
||||
@@ -61,7 +60,7 @@ namespace Umbraco.Tests.Common.Builders
|
||||
|
||||
public override IUserGroup Build()
|
||||
{
|
||||
return Mock.Of<IUserGroup>(x =>
|
||||
var userGroup = Mock.Of<IUserGroup>(x =>
|
||||
x.StartContentId == _startContentId &&
|
||||
x.StartMediaId == _startMediaId &&
|
||||
x.Name == (_name ?? ("TestUserGroup" + _suffix)) &&
|
||||
@@ -69,9 +68,10 @@ namespace Umbraco.Tests.Common.Builders
|
||||
x.Icon == _icon &&
|
||||
x.Permissions == _permissions &&
|
||||
x.AllowedSections == _sectionCollection);
|
||||
return userGroup;
|
||||
}
|
||||
|
||||
int? IWithIdBuilder.Id
|
||||
int? IWithIdBuilder.Id
|
||||
{
|
||||
get => _id;
|
||||
set => _id = value;
|
||||
|
||||
@@ -152,5 +152,14 @@ namespace Umbraco.Tests.Common
|
||||
|
||||
return mock.Object;
|
||||
}
|
||||
|
||||
public ILoggingConfiguration GetLoggingConfiguration(IHostingEnvironment hostingEnv = null)
|
||||
{
|
||||
hostingEnv = hostingEnv ?? GetHostingEnvironment();
|
||||
return new LoggingConfiguration(
|
||||
Path.Combine(hostingEnv.ApplicationPhysicalPath, "App_Data\\Logs"),
|
||||
Path.Combine(hostingEnv.ApplicationPhysicalPath, "config\\serilog.config"),
|
||||
Path.Combine(hostingEnv.ApplicationPhysicalPath, "config\\serilog.user.config"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Web.Common.AspNetCore;
|
||||
using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using System;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Tests.Integration.Testing;
|
||||
using Umbraco.Tests.Testing;
|
||||
|
||||
namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
@@ -89,7 +87,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var user = UserBuilder.Build();
|
||||
var user = UserBuilder.WithoutIdentity().Build();
|
||||
repository.Save(user);
|
||||
|
||||
|
||||
@@ -367,10 +365,9 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
private User CreateAndCommitUserWithGroup(IUserRepository repository, IUserGroupRepository userGroupRepository)
|
||||
{
|
||||
var user = UserBuilder.Build();
|
||||
var user = UserBuilder.WithoutIdentity().Build();
|
||||
repository.Save(user);
|
||||
|
||||
|
||||
var group = UserGroupBuilder.Build();
|
||||
userGroupRepository.AddOrUpdateGroupWithUsers(@group, new[] { user.Id });
|
||||
|
||||
@@ -381,9 +378,9 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
private IUser[] CreateAndCommitMultipleUsers(IUserRepository repository)
|
||||
{
|
||||
var user1 = UserBuilder.WithSuffix("1").Build();
|
||||
var user2 = UserBuilder.WithSuffix("2").Build();
|
||||
var user3 = UserBuilder.WithSuffix("3").Build();
|
||||
var user1 = UserBuilder.WithoutIdentity().WithSuffix("1").Build();
|
||||
var user2 = UserBuilder.WithoutIdentity().WithSuffix("2").Build();
|
||||
var user3 = UserBuilder.WithoutIdentity().WithSuffix("3").Build();
|
||||
repository.Save(user1);
|
||||
repository.Save(user2);
|
||||
repository.Save(user3);
|
||||
|
||||
@@ -4,8 +4,10 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Smidge;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Runtime;
|
||||
@@ -57,7 +59,7 @@ namespace Umbraco.Tests.Integration
|
||||
var coreRuntime = new CoreRuntime(testHelper.GetConfigs(), testHelper.GetUmbracoVersion(),
|
||||
testHelper.IOHelper, testHelper.Logger, testHelper.Profiler, testHelper.UmbracoBootPermissionChecker,
|
||||
testHelper.GetHostingEnvironment(), testHelper.GetBackOfficeInfo(), testHelper.DbProviderFactoryCreator,
|
||||
testHelper.MainDom, testHelper.GetTypeFinder());
|
||||
testHelper.MainDom, testHelper.GetTypeFinder(), NoAppCache.Instance);
|
||||
|
||||
// boot it!
|
||||
var factory = coreRuntime.Configure(umbracoContainer);
|
||||
@@ -99,7 +101,7 @@ namespace Umbraco.Tests.Integration
|
||||
|
||||
// Add it!
|
||||
services.AddUmbracoConfiguration(hostContext.Configuration);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
|
||||
});
|
||||
|
||||
var host = await hostBuilder.StartAsync();
|
||||
@@ -138,7 +140,7 @@ namespace Umbraco.Tests.Integration
|
||||
|
||||
// Add it!
|
||||
services.AddUmbracoConfiguration(hostContext.Configuration);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
|
||||
});
|
||||
|
||||
var host = await hostBuilder.StartAsync();
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NPoco.Expressions;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -21,7 +18,6 @@ using Umbraco.Tests.Common.Builders;
|
||||
using Umbraco.Tests.Integration.Extensions;
|
||||
using Umbraco.Tests.Integration.Implementations;
|
||||
using Umbraco.Web.BackOffice.AspNetCore;
|
||||
using Umbraco.Web.Common.AspNetCore;
|
||||
using Umbraco.Web.Common.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Testing
|
||||
@@ -112,7 +108,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
|
||||
// Add it!
|
||||
services.AddUmbracoConfiguration(hostContext.Configuration);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _);
|
||||
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
|
||||
});
|
||||
|
||||
var host = await hostBuilder.StartAsync();
|
||||
|
||||
@@ -9,7 +9,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models
|
||||
[TestFixture]
|
||||
public class DataTypeTests
|
||||
{
|
||||
private readonly DataTypeBuilder _builder = new DataTypeBuilder();
|
||||
private DataTypeBuilder _builder;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_builder = new DataTypeBuilder();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Deep_Clone()
|
||||
|
||||
@@ -9,7 +9,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models
|
||||
[TestFixture]
|
||||
public class DictionaryItemTests
|
||||
{
|
||||
private readonly DictionaryItemBuilder _builder = new DictionaryItemBuilder();
|
||||
private DictionaryItemBuilder _builder = new DictionaryItemBuilder();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_builder = new DictionaryItemBuilder();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Deep_Clone()
|
||||
|
||||
@@ -8,7 +8,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models
|
||||
[TestFixture]
|
||||
public class LanguageTests
|
||||
{
|
||||
private readonly LanguageBuilder _builder = new LanguageBuilder();
|
||||
private LanguageBuilder _builder = new LanguageBuilder();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_builder = new LanguageBuilder();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Deep_Clone()
|
||||
|
||||
+29
-9
@@ -3,18 +3,42 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Common.Builders;
|
||||
using Umbraco.Tests.Common.Builders.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.Models
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models
|
||||
{
|
||||
[TestFixture]
|
||||
public class MacroTests
|
||||
{
|
||||
private MacroBuilder _builder;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_builder = new MacroBuilder();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Deep_Clone()
|
||||
{
|
||||
var macro = new Macro(TestHelper.ShortStringHelper, 1, Guid.NewGuid(), true, 3, "test", "Test", false, true, true, "~/script.cshtml");
|
||||
macro.Properties.Add(new MacroProperty(6, Guid.NewGuid(), "rewq", "REWQ", 1, "asdfasdf"));
|
||||
var macro = _builder
|
||||
.WithId(1)
|
||||
.WithUseInEditor(true)
|
||||
.WithCacheDuration(3)
|
||||
.WithAlias("test")
|
||||
.WithName("Test")
|
||||
.WithSource("~/script.cshtml")
|
||||
.WithCacheByMember(true)
|
||||
.WithDontRender(true)
|
||||
.AddProperty()
|
||||
.WithId(6)
|
||||
.WithAlias("rewq")
|
||||
.WithName("REWQ")
|
||||
.WithSortOrder(1)
|
||||
.WithEditorAlias("asdfasdf")
|
||||
.Done()
|
||||
.Build();
|
||||
|
||||
var clone = (Macro)macro.DeepClone();
|
||||
|
||||
@@ -24,7 +48,7 @@ namespace Umbraco.Tests.Models
|
||||
|
||||
Assert.AreEqual(clone.Properties.Count, macro.Properties.Count);
|
||||
|
||||
for (int i = 0; i < clone.Properties.Count; i++)
|
||||
for (var i = 0; i < clone.Properties.Count; i++)
|
||||
{
|
||||
Assert.AreEqual(clone.Properties[i], macro.Properties[i]);
|
||||
Assert.AreNotSame(clone.Properties[i], macro.Properties[i]);
|
||||
@@ -37,9 +61,7 @@ namespace Umbraco.Tests.Models
|
||||
//This double verifies by reflection
|
||||
var allProps = clone.GetType().GetProperties();
|
||||
foreach (var propertyInfo in allProps)
|
||||
{
|
||||
Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(macro, null));
|
||||
}
|
||||
|
||||
//need to ensure the event handlers are wired
|
||||
|
||||
@@ -51,8 +73,6 @@ namespace Umbraco.Tests.Models
|
||||
Assert.AreEqual(1, clone.AddedProperties.Count());
|
||||
clone.Properties.Remove("rewq");
|
||||
Assert.AreEqual(1, clone.RemovedProperties.Count());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user