Revert "Temp8 tinymce"
This commit is contained in:
@@ -1,240 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public sealed class AuditService : ScopeRepositoryService, IAuditService
|
||||
{
|
||||
private readonly Lazy<bool> _isAvailable;
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
private readonly IAuditEntryRepository _auditEntryRepository;
|
||||
|
||||
public AuditService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IAuditRepository auditRepository, IAuditEntryRepository auditEntryRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_auditRepository = auditRepository;
|
||||
_auditEntryRepository = auditEntryRepository;
|
||||
_isAvailable = new Lazy<bool>(DetermineIsAvailable);
|
||||
}
|
||||
|
||||
public void Add(AuditType type, int userId, int objectId, string entityType, string comment, string parameters = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_auditRepository.Save(new AuditItem(objectId, type, userId, entityType, comment, parameters));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IAuditItem> GetLogs(int objectId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var result = _auditRepository.Get(Query<IAuditItem>().Where(x => x.Id == objectId));
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IAuditItem> GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var result = sinceDate.HasValue == false
|
||||
? _auditRepository.Get(Query<IAuditItem>().Where(x => x.UserId == userId && x.AuditType == type))
|
||||
: _auditRepository.Get(Query<IAuditItem>().Where(x => x.UserId == userId && x.AuditType == type && x.CreateDate >= sinceDate.Value));
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IAuditItem> GetLogs(AuditType type, DateTime? sinceDate = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var result = sinceDate.HasValue == false
|
||||
? _auditRepository.Get(Query<IAuditItem>().Where(x => x.AuditType == type))
|
||||
: _auditRepository.Get(Query<IAuditItem>().Where(x => x.AuditType == type && x.CreateDate >= sinceDate.Value));
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanLogs(int maximumAgeOfLogsInMinutes)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_auditRepository.CleanLogs(maximumAgeOfLogsInMinutes);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given entity
|
||||
/// </summary>
|
||||
/// <param name="entityId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
|
||||
Direction orderDirection = Direction.Descending,
|
||||
AuditType[] auditTypeFilter = null,
|
||||
IQuery<IAuditItem> customFilter = null)
|
||||
{
|
||||
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
|
||||
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
|
||||
|
||||
if (entityId == Constants.System.Root || entityId <= 0)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Enumerable.Empty<IAuditItem>();
|
||||
}
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IAuditItem>().Where(x => x.Id == entityId);
|
||||
|
||||
return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns paged items in the audit trail for a given user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="pageIndex"></param>
|
||||
/// <param name="pageSize"></param>
|
||||
/// <param name="totalRecords"></param>
|
||||
/// <param name="orderDirection">
|
||||
/// By default this will always be ordered descending (newest first)
|
||||
/// </param>
|
||||
/// <param name="auditTypeFilter">
|
||||
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
|
||||
/// so we need to do that here
|
||||
/// </param>
|
||||
/// <param name="customFilter">
|
||||
/// Optional filter to be applied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[] auditTypeFilter = null, IQuery<IAuditItem> customFilter = null)
|
||||
{
|
||||
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
|
||||
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
|
||||
|
||||
if (userId < 0)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Enumerable.Empty<IAuditItem>();
|
||||
}
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IAuditItem>().Where(x => x.UserId == userId);
|
||||
|
||||
return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string affectedDetails, string eventType, string eventDetails)
|
||||
{
|
||||
if (performingUserId < 0 && performingUserId != Constants.Security.SuperUserId) throw new ArgumentOutOfRangeException(nameof(performingUserId));
|
||||
if (string.IsNullOrWhiteSpace(perfomingDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(perfomingDetails));
|
||||
if (string.IsNullOrWhiteSpace(eventType)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventType));
|
||||
if (string.IsNullOrWhiteSpace(eventDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventDetails));
|
||||
|
||||
//we need to truncate the data else we'll get SQL errors
|
||||
affectedDetails = affectedDetails?.Substring(0, Math.Min(affectedDetails.Length, AuditEntryDto.DetailsLength));
|
||||
eventDetails = eventDetails.Substring(0, Math.Min(eventDetails.Length, AuditEntryDto.DetailsLength));
|
||||
|
||||
//validate the eventType - must contain a forward slash, no spaces, no special chars
|
||||
var eventTypeParts = eventType.ToCharArray();
|
||||
if (eventTypeParts.Contains('/') == false || eventTypeParts.All(c => char.IsLetterOrDigit(c) || c == '/' || c == '-') == false)
|
||||
throw new ArgumentException(nameof(eventType) + " must contain only alphanumeric characters, hyphens and at least one '/' defining a category");
|
||||
if (eventType.Length > AuditEntryDto.EventTypeLength)
|
||||
throw new ArgumentException($"Must be max {AuditEntryDto.EventTypeLength} chars.", nameof(eventType));
|
||||
if (performingIp != null && performingIp.Length > AuditEntryDto.IpLength)
|
||||
throw new ArgumentException($"Must be max {AuditEntryDto.EventTypeLength} chars.", nameof(performingIp));
|
||||
|
||||
var entry = new AuditEntry
|
||||
{
|
||||
PerformingUserId = performingUserId,
|
||||
PerformingDetails = perfomingDetails,
|
||||
PerformingIp = performingIp,
|
||||
EventDateUtc = eventDateUtc,
|
||||
AffectedUserId = affectedUserId,
|
||||
AffectedDetails = affectedDetails,
|
||||
EventType = eventType,
|
||||
EventDetails = eventDetails
|
||||
};
|
||||
|
||||
if (_isAvailable.Value == false) return entry;
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_auditEntryRepository.Save(entry);
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
//TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
|
||||
internal IEnumerable<IAuditEntry> GetAll()
|
||||
{
|
||||
if (_isAvailable.Value == false) return Enumerable.Empty<IAuditEntry>();
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _auditEntryRepository.GetMany();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
|
||||
internal IEnumerable<IAuditEntry> GetPage(long pageIndex, int pageCount, out long records)
|
||||
{
|
||||
if (_isAvailable.Value == false)
|
||||
{
|
||||
records = 0;
|
||||
return Enumerable.Empty<IAuditEntry>();
|
||||
}
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _auditEntryRepository.GetPage(pageIndex, pageCount, out records);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the repository is available.
|
||||
/// </summary>
|
||||
private bool DetermineIsAvailable()
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _auditEntryRepository.IsAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements <see cref="IConsentService"/>.
|
||||
/// </summary>
|
||||
internal class ConsentService : ScopeRepositoryService, IConsentService
|
||||
{
|
||||
private readonly IConsentRepository _consentRepository;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentService"/> class.
|
||||
/// </summary>
|
||||
public ConsentService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IConsentRepository consentRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_consentRepository = consentRepository;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConsent RegisterConsent(string source, string context, string action, ConsentState state, string comment = null)
|
||||
{
|
||||
// prevent stupid states
|
||||
var v = 0;
|
||||
if ((state & ConsentState.Pending) > 0) v++;
|
||||
if ((state & ConsentState.Granted) > 0) v++;
|
||||
if ((state & ConsentState.Revoked) > 0) v++;
|
||||
if (v != 1)
|
||||
throw new ArgumentException("Invalid state.", nameof(state));
|
||||
|
||||
var consent = new Consent
|
||||
{
|
||||
Current = true,
|
||||
Source = source,
|
||||
Context = context,
|
||||
Action = action,
|
||||
CreateDate = DateTime.Now,
|
||||
State = state,
|
||||
Comment = comment
|
||||
};
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_consentRepository.ClearCurrent(source, context, action);
|
||||
_consentRepository.Save(consent);
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return consent;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IConsent> LookupConsent(string source = null, string context = null, string action = null,
|
||||
bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
|
||||
bool includeHistory = false)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IConsent>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(source) == false)
|
||||
query = sourceStartsWith ? query.Where(x => x.Source.StartsWith(source)) : query.Where(x => x.Source == source);
|
||||
if (string.IsNullOrWhiteSpace(context) == false)
|
||||
query = contextStartsWith ? query.Where(x => x.Context.StartsWith(context)) : query.Where(x => x.Context == context);
|
||||
if (string.IsNullOrWhiteSpace(action) == false)
|
||||
query = actionStartsWith ? query.Where(x => x.Action.StartsWith(action)) : query.Where(x => x.Action == action);
|
||||
if (includeHistory == false)
|
||||
query = query.Where(x => x.Current);
|
||||
|
||||
return _consentRepository.Get(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the ContentType Service, which is an easy access to operations involving <see cref="IContentType"/>
|
||||
/// </summary>
|
||||
internal class ContentTypeService : ContentTypeServiceBase<IContentTypeRepository, IContentType, IContentTypeService>, IContentTypeService
|
||||
{
|
||||
public ContentTypeService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService,
|
||||
IContentTypeRepository repository, IAuditRepository auditRepository, IDocumentTypeContainerRepository entityContainerRepository, IEntityRepository entityRepository)
|
||||
: base(provider, logger, eventMessagesFactory, repository, auditRepository, entityContainerRepository, entityRepository)
|
||||
{
|
||||
ContentService = contentService;
|
||||
}
|
||||
|
||||
protected override IContentTypeService This => this;
|
||||
|
||||
// beware! order is important to avoid deadlocks
|
||||
protected override int[] ReadLockIds { get; } = { Constants.Locks.ContentTypes };
|
||||
protected override int[] WriteLockIds { get; } = { Constants.Locks.ContentTree, Constants.Locks.ContentTypes };
|
||||
|
||||
private IContentService ContentService { get; }
|
||||
|
||||
protected override Guid ContainedObjectType => Constants.ObjectTypes.DocumentType;
|
||||
|
||||
protected override void DeleteItemsOfTypes(IEnumerable<int> typeIds)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var typeIdsA = typeIds.ToArray();
|
||||
ContentService.DeleteOfTypes(typeIdsA);
|
||||
ContentService.DeleteBlueprintsOfTypes(typeIdsA);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all property type aliases accross content, media and member types.
|
||||
/// </summary>
|
||||
/// <returns>All property type aliases.</returns>
|
||||
/// <remarks>Beware! Works accross content, media and member types.</remarks>
|
||||
public IEnumerable<string> GetAllPropertyTypeAliases()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
// that one is special because it works accross content, media and member types
|
||||
scope.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes);
|
||||
return Repository.GetAllPropertyTypeAliases();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all content type aliases accross content, media and member types.
|
||||
/// </summary>
|
||||
/// <param name="guids">Optional object types guid to restrict to content, and/or media, and/or member types.</param>
|
||||
/// <returns>All content type aliases.</returns>
|
||||
/// <remarks>Beware! Works accross content, media and member types.</remarks>
|
||||
public IEnumerable<string> GetAllContentTypeAliases(params Guid[] guids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
// that one is special because it works accross content, media and member types
|
||||
scope.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes);
|
||||
return Repository.GetAllContentTypeAliases(guids);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all content type id for aliases accross content, media and member types.
|
||||
/// </summary>
|
||||
/// <param name="aliases">Aliases to look for.</param>
|
||||
/// <returns>All content type ids.</returns>
|
||||
/// <remarks>Beware! Works accross content, media and member types.</remarks>
|
||||
public IEnumerable<int> GetAllContentTypeIds(string[] aliases)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
// that one is special because it works accross content, media and member types
|
||||
scope.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes);
|
||||
return Repository.GetAllContentTypeIds(aliases);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
internal abstract class ContentTypeServiceBase : ScopeRepositoryService
|
||||
{
|
||||
protected ContentTypeServiceBase(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
internal abstract class ContentTypeServiceBase<TItem, TService> : ContentTypeServiceBase
|
||||
where TItem : class, IContentTypeComposition
|
||||
where TService : class, IContentTypeServiceBase<TItem>
|
||||
{
|
||||
protected ContentTypeServiceBase(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{ }
|
||||
|
||||
protected abstract TService This { get; }
|
||||
|
||||
// that one must be dispatched
|
||||
internal static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> Changed;
|
||||
|
||||
// that one is always immediate (transactional)
|
||||
public static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> ScopedRefreshedEntity;
|
||||
|
||||
// used by tests to clear events
|
||||
internal static void ClearScopeEvents()
|
||||
{
|
||||
ScopedRefreshedEntity = null;
|
||||
}
|
||||
|
||||
// these must be dispatched
|
||||
public static event TypedEventHandler<TService, SaveEventArgs<TItem>> Saving;
|
||||
public static event TypedEventHandler<TService, SaveEventArgs<TItem>> Saved;
|
||||
public static event TypedEventHandler<TService, DeleteEventArgs<TItem>> Deleting;
|
||||
public static event TypedEventHandler<TService, DeleteEventArgs<TItem>> Deleted;
|
||||
public static event TypedEventHandler<TService, MoveEventArgs<TItem>> Moving;
|
||||
public static event TypedEventHandler<TService, MoveEventArgs<TItem>> Moved;
|
||||
public static event TypedEventHandler<TService, SaveEventArgs<EntityContainer>> SavingContainer;
|
||||
public static event TypedEventHandler<TService, SaveEventArgs<EntityContainer>> SavedContainer;
|
||||
public static event TypedEventHandler<TService, DeleteEventArgs<EntityContainer>> DeletingContainer;
|
||||
public static event TypedEventHandler<TService, DeleteEventArgs<EntityContainer>> DeletedContainer;
|
||||
|
||||
protected void OnChanged(IScope scope, ContentTypeChange<TItem>.EventArgs args)
|
||||
{
|
||||
scope.Events.Dispatch(Changed, This, args, nameof(Changed));
|
||||
}
|
||||
|
||||
protected void OnUowRefreshedEntity(ContentTypeChange<TItem>.EventArgs args)
|
||||
{
|
||||
// that one is always immediate (not dispatched, transactional)
|
||||
ScopedRefreshedEntity.RaiseEvent(args, This);
|
||||
}
|
||||
|
||||
protected bool OnSavingCancelled(IScope scope, SaveEventArgs<TItem> args)
|
||||
{
|
||||
return scope.Events.DispatchCancelable(Saving, This, args);
|
||||
}
|
||||
|
||||
protected void OnSaved(IScope scope, SaveEventArgs<TItem> args)
|
||||
{
|
||||
scope.Events.Dispatch(Saved, This, args);
|
||||
}
|
||||
|
||||
protected bool OnDeletingCancelled(IScope scope, DeleteEventArgs<TItem> args)
|
||||
{
|
||||
return scope.Events.DispatchCancelable(Deleting, This, args, nameof(Deleting));
|
||||
}
|
||||
|
||||
protected void OnDeleted(IScope scope, DeleteEventArgs<TItem> args)
|
||||
{
|
||||
scope.Events.Dispatch(Deleted, This, args);
|
||||
}
|
||||
|
||||
protected bool OnMovingCancelled(IScope scope, MoveEventArgs<TItem> args)
|
||||
{
|
||||
return scope.Events.DispatchCancelable(Moving, This, args);
|
||||
}
|
||||
|
||||
protected void OnMoved(IScope scope, MoveEventArgs<TItem> args)
|
||||
{
|
||||
scope.Events.Dispatch(Moved, This, args);
|
||||
}
|
||||
|
||||
protected bool OnSavingContainerCancelled(IScope scope, SaveEventArgs<EntityContainer> args)
|
||||
{
|
||||
return scope.Events.DispatchCancelable(SavingContainer, This, args, nameof(SavingContainer));
|
||||
}
|
||||
|
||||
protected void OnSavedContainer(IScope scope, SaveEventArgs<EntityContainer> args)
|
||||
{
|
||||
scope.Events.Dispatch(SavedContainer, This, args);
|
||||
}
|
||||
|
||||
protected bool OnRenamingContainerCancelled(IScope scope, SaveEventArgs<EntityContainer> args)
|
||||
{
|
||||
return scope.Events.DispatchCancelable(SavedContainer, This, args, nameof(SavedContainer));
|
||||
}
|
||||
|
||||
protected void OnRenamedContainer(IScope scope, SaveEventArgs<EntityContainer> args)
|
||||
{
|
||||
scope.Events.Dispatch(SavedContainer, This, args, nameof(SavedContainer));
|
||||
}
|
||||
|
||||
protected bool OnDeletingContainerCancelled(IScope scope, DeleteEventArgs<EntityContainer> args)
|
||||
{
|
||||
return scope.Events.DispatchCancelable(DeletingContainer, This, args);
|
||||
}
|
||||
|
||||
protected void OnDeletedContainer(IScope scope, DeleteEventArgs<EntityContainer> args)
|
||||
{
|
||||
scope.Events.Dispatch(DeletedContainer, This, args, nameof(DeletedContainer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,955 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
internal abstract class ContentTypeServiceBase<TRepository, TItem, TService> : ContentTypeServiceBase<TItem, TService>, IContentTypeServiceBase<TItem>
|
||||
where TRepository : IContentTypeRepositoryBase<TItem>
|
||||
where TItem : class, IContentTypeComposition
|
||||
where TService : class, IContentTypeServiceBase<TItem>
|
||||
{
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
private readonly IEntityContainerRepository _containerRepository;
|
||||
private readonly IEntityRepository _entityRepository;
|
||||
|
||||
protected ContentTypeServiceBase(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
TRepository repository, IAuditRepository auditRepository, IEntityContainerRepository containerRepository, IEntityRepository entityRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
Repository = repository;
|
||||
_auditRepository = auditRepository;
|
||||
_containerRepository = containerRepository;
|
||||
_entityRepository = entityRepository;
|
||||
}
|
||||
|
||||
protected TRepository Repository { get; }
|
||||
protected abstract int[] WriteLockIds { get; }
|
||||
protected abstract int[] ReadLockIds { get; }
|
||||
|
||||
#region Validation
|
||||
|
||||
public Attempt<string[]> ValidateComposition(TItem compo)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
ValidateLocked(compo);
|
||||
}
|
||||
return Attempt<string[]>.Succeed();
|
||||
}
|
||||
catch (InvalidCompositionException ex)
|
||||
{
|
||||
return Attempt.Fail(ex.PropertyTypeAliases, ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ValidateLocked(TItem compositionContentType)
|
||||
{
|
||||
// performs business-level validation of the composition
|
||||
// should ensure that it is absolutely safe to save the composition
|
||||
|
||||
// eg maybe a property has been added, with an alias that's OK (no conflict with ancestors)
|
||||
// but that cannot be used (conflict with descendants)
|
||||
|
||||
var allContentTypes = Repository.GetMany(new int[0]).Cast<IContentTypeComposition>().ToArray();
|
||||
|
||||
var compositionAliases = compositionContentType.CompositionAliases();
|
||||
var compositions = allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y)));
|
||||
var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).ToArray();
|
||||
var indirectReferences = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id));
|
||||
var comparer = new DelegateEqualityComparer<IContentTypeComposition>((x, y) => x.Id == y.Id, x => x.Id);
|
||||
var dependencies = new HashSet<IContentTypeComposition>(compositions, comparer);
|
||||
var stack = new Stack<IContentTypeComposition>();
|
||||
foreach (var indirectReference in indirectReferences)
|
||||
stack.Push(indirectReference); // push indirect references to a stack, so we can add recursively
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var indirectReference = stack.Pop();
|
||||
dependencies.Add(indirectReference);
|
||||
|
||||
// get all compositions for the current indirect reference
|
||||
var directReferences = indirectReference.ContentTypeComposition;
|
||||
foreach (var directReference in directReferences)
|
||||
{
|
||||
if (directReference.Id == compositionContentType.Id || directReference.Alias.Equals(compositionContentType.Alias)) continue;
|
||||
dependencies.Add(directReference);
|
||||
// a direct reference has compositions of its own - these also need to be taken into account
|
||||
var directReferenceGraph = directReference.CompositionAliases();
|
||||
foreach (var c in allContentTypes.Where(x => directReferenceGraph.Any(y => x.Alias.Equals(y, StringComparison.InvariantCultureIgnoreCase))))
|
||||
dependencies.Add(c);
|
||||
}
|
||||
|
||||
// recursive lookup of indirect references
|
||||
foreach (var c in allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == indirectReference.Id)))
|
||||
stack.Push(c);
|
||||
}
|
||||
|
||||
foreach (var dependency in dependencies)
|
||||
{
|
||||
if (dependency.Id == compositionContentType.Id) continue;
|
||||
var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (contentTypeDependency == null) continue;
|
||||
var intersect = contentTypeDependency.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).Intersect(propertyTypeAliases).ToArray();
|
||||
if (intersect.Length == 0) continue;
|
||||
|
||||
throw new InvalidCompositionException(compositionContentType.Alias, intersect.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Composition
|
||||
|
||||
internal IEnumerable<ContentTypeChange<TItem>> ComposeContentTypeChanges(params TItem[] contentTypes)
|
||||
{
|
||||
// find all content types impacted by the changes,
|
||||
// - content type alias changed
|
||||
// - content type property removed, or alias changed
|
||||
// - content type composition removed (not testing if composition had properties...)
|
||||
// - content type variation changed
|
||||
// - property type variation changed
|
||||
//
|
||||
// because these are the changes that would impact the raw content data
|
||||
|
||||
// note
|
||||
// this is meant to run *after* uow.Commit() so must use WasPropertyDirty() everywhere
|
||||
// instead of IsPropertyDirty() since dirty properties have been resetted already
|
||||
|
||||
var changes = new List<ContentTypeChange<TItem>>();
|
||||
|
||||
foreach (var contentType in contentTypes)
|
||||
{
|
||||
var dirty = (IRememberBeingDirty)contentType;
|
||||
|
||||
// skip new content types
|
||||
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
|
||||
var isNewContentType = dirty.WasPropertyDirty("Id");
|
||||
if (isNewContentType)
|
||||
{
|
||||
AddChange(changes, contentType, ContentTypeChangeTypes.Create);
|
||||
continue;
|
||||
}
|
||||
|
||||
// alias change?
|
||||
var hasAliasChanged = dirty.WasPropertyDirty("Alias");
|
||||
|
||||
// existing property alias change?
|
||||
var hasAnyPropertyChangedAlias = contentType.PropertyTypes.Any(propertyType =>
|
||||
{
|
||||
if (!(propertyType is IRememberBeingDirty dirtyProperty))
|
||||
throw new Exception("oops");
|
||||
|
||||
// skip new properties
|
||||
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
|
||||
var isNewProperty = dirtyProperty.WasPropertyDirty("Id");
|
||||
if (isNewProperty) return false;
|
||||
|
||||
// alias change?
|
||||
return dirtyProperty.WasPropertyDirty("Alias");
|
||||
});
|
||||
|
||||
// removed properties?
|
||||
var hasAnyPropertyBeenRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved");
|
||||
|
||||
// removed compositions?
|
||||
var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved");
|
||||
|
||||
// variation changed?
|
||||
var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations");
|
||||
|
||||
// property variation change?
|
||||
var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged();
|
||||
|
||||
// main impact on properties?
|
||||
var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged
|
||||
|| hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias;
|
||||
|
||||
if (hasAliasChanged || hasPropertyMainImpact)
|
||||
{
|
||||
// add that one, as a main change
|
||||
AddChange(changes, contentType, ContentTypeChangeTypes.RefreshMain);
|
||||
|
||||
if (hasPropertyMainImpact)
|
||||
foreach (var c in GetComposedOf(contentType.Id))
|
||||
AddChange(changes, c, ContentTypeChangeTypes.RefreshMain);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add that one, as an other change
|
||||
AddChange(changes, contentType, ContentTypeChangeTypes.RefreshOther);
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
// ensures changes contains no duplicates
|
||||
private static void AddChange(ICollection<ContentTypeChange<TItem>> changes, TItem contentType, ContentTypeChangeTypes changeTypes)
|
||||
{
|
||||
var change = changes.FirstOrDefault(x => x.Item == contentType);
|
||||
if (change == null)
|
||||
{
|
||||
changes.Add(new ContentTypeChange<TItem>(contentType, changeTypes));
|
||||
return;
|
||||
}
|
||||
change.ChangeTypes |= changeTypes;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get, Has, Is, Count
|
||||
|
||||
public TItem Get(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
return Repository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public TItem Get(string alias)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
return Repository.Get(alias);
|
||||
}
|
||||
}
|
||||
|
||||
public TItem Get(Guid id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
return Repository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetAll(params int[] ids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
return Repository.GetMany(ids);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetAll(params Guid[] ids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
return Repository.GetMany(ids);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetChildren(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
var query = Query<TItem>().Where(x => x.ParentId == id);
|
||||
return Repository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetChildren(Guid id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
var found = Get(id);
|
||||
if (found == null) return Enumerable.Empty<TItem>();
|
||||
var query = Query<TItem>().Where(x => x.ParentId == found.Id);
|
||||
return Repository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasChildren(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
var query = Query<TItem>().Where(x => x.ParentId == id);
|
||||
var count = Repository.Count(query);
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasChildren(Guid id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
var found = Get(id);
|
||||
if (found == null) return false;
|
||||
var query = Query<TItem>().Where(x => x.ParentId == found.Id);
|
||||
var count = Repository.Count(query);
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the path of a content item, this will return true if the content item exists underneath a list view content item
|
||||
/// </summary>
|
||||
/// <param name="contentPath"></param>
|
||||
/// <returns></returns>
|
||||
public bool HasContainerInPath(string contentPath)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
// can use same repo for both content and media
|
||||
return Repository.HasContainerInPath(contentPath);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetDescendants(int id, bool andSelf)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
|
||||
var descendants = new List<TItem>();
|
||||
if (andSelf) descendants.Add(Repository.Get(id));
|
||||
var ids = new Stack<int>();
|
||||
ids.Push(id);
|
||||
|
||||
while (ids.Count > 0)
|
||||
{
|
||||
var i = ids.Pop();
|
||||
var query = Query<TItem>().Where(x => x.ParentId == i);
|
||||
var result = Repository.Get(query).ToArray();
|
||||
|
||||
foreach (var c in result)
|
||||
{
|
||||
descendants.Add(c);
|
||||
ids.Push(c.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return descendants.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetComposedOf(int id, IEnumerable<TItem> all)
|
||||
{
|
||||
return all.Where(x => x.ContentTypeComposition.Any(y => y.Id == id));
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<TItem> GetComposedOf(int id)
|
||||
{
|
||||
// GetAll is cheap, repository has a full dataset cache policy
|
||||
// fixme - still, because it uses the cache, race conditions!
|
||||
var allContentTypes = GetAll(Array.Empty<int>());
|
||||
return GetComposedOf(id, allContentTypes);
|
||||
}
|
||||
|
||||
public int Count()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
return Repository.Count(Query<TItem>());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Save
|
||||
|
||||
public void Save(TItem item, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<TItem>(item);
|
||||
if (OnSavingCancelled(scope, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.Name))
|
||||
throw new ArgumentException("Cannot save item with empty name.");
|
||||
|
||||
scope.WriteLock(WriteLockIds);
|
||||
|
||||
// validate the DAG transform, within the lock
|
||||
ValidateLocked(item); // throws if invalid
|
||||
|
||||
item.CreatorId = userId;
|
||||
if (item.Description == string.Empty) item.Description = null;
|
||||
Repository.Save(item); // also updates content/media/member items
|
||||
|
||||
// figure out impacted content types
|
||||
var changes = ComposeContentTypeChanges(item).ToArray();
|
||||
var args = changes.ToEventArgs();
|
||||
|
||||
OnUowRefreshedEntity(args);
|
||||
|
||||
OnChanged(scope, args);
|
||||
saveEventArgs.CanCancel = false;
|
||||
OnSaved(scope, saveEventArgs);
|
||||
|
||||
Audit(AuditType.Save, userId, item.Id);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(IEnumerable<TItem> items, int userId = 0)
|
||||
{
|
||||
var itemsA = items.ToArray();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<TItem>(itemsA);
|
||||
if (OnSavingCancelled(scope, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
scope.WriteLock(WriteLockIds);
|
||||
|
||||
// all-or-nothing, validate them all first
|
||||
foreach (var contentType in itemsA)
|
||||
{
|
||||
ValidateLocked(contentType); // throws if invalid
|
||||
}
|
||||
foreach (var contentType in itemsA)
|
||||
{
|
||||
contentType.CreatorId = userId;
|
||||
if (contentType.Description == string.Empty) contentType.Description = null;
|
||||
Repository.Save(contentType);
|
||||
}
|
||||
|
||||
// figure out impacted content types
|
||||
var changes = ComposeContentTypeChanges(itemsA).ToArray();
|
||||
var args = changes.ToEventArgs();
|
||||
|
||||
OnUowRefreshedEntity(args);
|
||||
|
||||
OnChanged(scope, args);
|
||||
saveEventArgs.CanCancel = false;
|
||||
OnSaved(scope, saveEventArgs);
|
||||
|
||||
Audit(AuditType.Save, userId, -1);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delete
|
||||
|
||||
public void Delete(TItem item, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<TItem>(item);
|
||||
if (OnDeletingCancelled(scope, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
scope.WriteLock(WriteLockIds);
|
||||
|
||||
// all descendants are going to be deleted
|
||||
var descendantsAndSelf = GetDescendants(item.Id, true)
|
||||
.ToArray();
|
||||
var deleted = descendantsAndSelf;
|
||||
|
||||
// all impacted (through composition) probably lose some properties
|
||||
// don't try to be too clever here, just report them all
|
||||
// do this before anything is deleted
|
||||
var changed = descendantsAndSelf.SelectMany(xx => GetComposedOf(xx.Id))
|
||||
.Distinct()
|
||||
.Except(descendantsAndSelf)
|
||||
.ToArray();
|
||||
|
||||
// delete content
|
||||
DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id));
|
||||
|
||||
// finally delete the content type
|
||||
// - recursively deletes all descendants
|
||||
// - deletes all associated property data
|
||||
// (contents of any descendant type have been deleted but
|
||||
// contents of any composed (impacted) type remain but
|
||||
// need to have their property data cleared)
|
||||
Repository.Delete(item);
|
||||
|
||||
//...
|
||||
var changes = descendantsAndSelf.Select(x => new ContentTypeChange<TItem>(x, ContentTypeChangeTypes.Remove))
|
||||
.Concat(changed.Select(x => new ContentTypeChange<TItem>(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)));
|
||||
var args = changes.ToEventArgs();
|
||||
|
||||
OnUowRefreshedEntity(args);
|
||||
|
||||
OnChanged(scope, args);
|
||||
deleteEventArgs.DeletedEntities = deleted.DistinctBy(x => x.Id);
|
||||
deleteEventArgs.CanCancel = false;
|
||||
OnDeleted(scope, deleteEventArgs);
|
||||
|
||||
Audit(AuditType.Delete, userId, item.Id);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(IEnumerable<TItem> items, int userId = 0)
|
||||
{
|
||||
var itemsA = items.ToArray();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<TItem>(itemsA);
|
||||
if (OnDeletingCancelled(scope, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
scope.WriteLock(WriteLockIds);
|
||||
|
||||
// all descendants are going to be deleted
|
||||
var allDescendantsAndSelf = itemsA.SelectMany(xx => GetDescendants(xx.Id, true))
|
||||
.DistinctBy(x => x.Id)
|
||||
.ToArray();
|
||||
var deleted = allDescendantsAndSelf;
|
||||
|
||||
// all impacted (through composition) probably lose some properties
|
||||
// don't try to be too clever here, just report them all
|
||||
// do this before anything is deleted
|
||||
var changed = allDescendantsAndSelf.SelectMany(x => GetComposedOf(x.Id))
|
||||
.Distinct()
|
||||
.Except(allDescendantsAndSelf)
|
||||
.ToArray();
|
||||
|
||||
// delete content
|
||||
DeleteItemsOfTypes(allDescendantsAndSelf.Select(x => x.Id));
|
||||
|
||||
// finally delete the content types
|
||||
// (see notes in overload)
|
||||
foreach (var item in itemsA)
|
||||
Repository.Delete(item);
|
||||
|
||||
var changes = allDescendantsAndSelf.Select(x => new ContentTypeChange<TItem>(x, ContentTypeChangeTypes.Remove))
|
||||
.Concat(changed.Select(x => new ContentTypeChange<TItem>(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)));
|
||||
var args = changes.ToEventArgs();
|
||||
|
||||
OnUowRefreshedEntity(args);
|
||||
|
||||
OnChanged(scope, args);
|
||||
deleteEventArgs.DeletedEntities = deleted.DistinctBy(x => x.Id);
|
||||
deleteEventArgs.CanCancel = false;
|
||||
OnDeleted(scope, deleteEventArgs);
|
||||
|
||||
Audit(AuditType.Delete, userId, -1);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void DeleteItemsOfTypes(IEnumerable<int> typeIds);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Copy
|
||||
|
||||
public TItem Copy(TItem original, string alias, string name, int parentId = -1)
|
||||
{
|
||||
TItem parent = null;
|
||||
if (parentId > 0)
|
||||
{
|
||||
parent = Get(parentId);
|
||||
if (parent == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find parent with id " + parentId);
|
||||
}
|
||||
}
|
||||
return Copy(original, alias, name, parent);
|
||||
}
|
||||
|
||||
public TItem Copy(TItem original, string alias, string name, TItem parent)
|
||||
{
|
||||
if (original == null) throw new ArgumentNullException(nameof(original));
|
||||
if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentNullOrEmptyException(nameof(alias));
|
||||
|
||||
if (parent != null && parent.HasIdentity == false)
|
||||
throw new InvalidOperationException("Parent must have an identity.");
|
||||
|
||||
// this is illegal
|
||||
//var originalb = (ContentTypeCompositionBase)original;
|
||||
// but we *know* it has to be a ContentTypeCompositionBase anyways
|
||||
var originalb = (ContentTypeCompositionBase) (object) original;
|
||||
var clone = (TItem) originalb.DeepCloneWithResetIdentities(alias);
|
||||
|
||||
clone.Name = name;
|
||||
|
||||
//remove all composition that is not it's current alias
|
||||
var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList();
|
||||
foreach (var a in compositionAliases)
|
||||
{
|
||||
clone.RemoveContentType(a);
|
||||
}
|
||||
|
||||
//if a parent is specified set it's composition and parent
|
||||
if (parent != null)
|
||||
{
|
||||
//add a new parent composition
|
||||
clone.AddContentType(parent);
|
||||
clone.ParentId = parent.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
//set to root
|
||||
clone.ParentId = -1;
|
||||
}
|
||||
|
||||
Save(clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public Attempt<OperationResult<MoveOperationStatusType, TItem>> Copy(TItem copying, int containerId)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
TItem copy;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(WriteLockIds);
|
||||
|
||||
try
|
||||
{
|
||||
if (containerId > 0)
|
||||
{
|
||||
var container = _containerRepository.Get(containerId);
|
||||
if (container == null)
|
||||
throw new DataOperationException<MoveOperationStatusType>(MoveOperationStatusType.FailedParentNotFound); // causes rollback
|
||||
}
|
||||
var alias = Repository.GetUniqueAlias(copying.Alias);
|
||||
|
||||
// this is illegal
|
||||
//var copyingb = (ContentTypeCompositionBase) copying;
|
||||
// but we *know* it has to be a ContentTypeCompositionBase anyways
|
||||
var copyingb = (ContentTypeCompositionBase) (object)copying;
|
||||
copy = (TItem) copyingb.DeepCloneWithResetIdentities(alias);
|
||||
|
||||
copy.Name = copy.Name + " (copy)"; // might not be unique
|
||||
|
||||
// if it has a parent, and the parent is a content type, unplug composition
|
||||
// all other compositions remain in place in the copied content type
|
||||
if (copy.ParentId > 0)
|
||||
{
|
||||
var parent = Repository.Get(copy.ParentId);
|
||||
if (parent != null)
|
||||
copy.RemoveContentType(parent.Alias);
|
||||
}
|
||||
|
||||
copy.ParentId = containerId;
|
||||
Repository.Save(copy);
|
||||
scope.Complete();
|
||||
}
|
||||
catch (DataOperationException<MoveOperationStatusType> ex)
|
||||
{
|
||||
return OperationResult.Attempt.Fail<MoveOperationStatusType, TItem>(ex.Operation, evtMsgs); // causes rollback
|
||||
}
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, copy);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Move
|
||||
|
||||
public Attempt<OperationResult<MoveOperationStatusType>> Move(TItem moving, int containerId)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
var moveInfo = new List<MoveEventInfo<TItem>>();
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var moveEventInfo = new MoveEventInfo<TItem>(moving, moving.Path, containerId);
|
||||
var moveEventArgs = new MoveEventArgs<TItem>(evtMsgs, moveEventInfo);
|
||||
if (OnMovingCancelled(scope, moveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs);
|
||||
}
|
||||
|
||||
scope.WriteLock(WriteLockIds); // also for containers
|
||||
|
||||
try
|
||||
{
|
||||
EntityContainer container = null;
|
||||
if (containerId > 0)
|
||||
{
|
||||
container = _containerRepository.Get(containerId);
|
||||
if (container == null)
|
||||
throw new DataOperationException<MoveOperationStatusType>(MoveOperationStatusType.FailedParentNotFound); // causes rollback
|
||||
}
|
||||
moveInfo.AddRange(Repository.Move(moving, container));
|
||||
scope.Complete();
|
||||
}
|
||||
catch (DataOperationException<MoveOperationStatusType> ex)
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Fail(ex.Operation, evtMsgs);
|
||||
}
|
||||
|
||||
// note: not raising any Changed event here because moving a content type under another container
|
||||
// has no impact on the published content types - would be entirely different if we were to support
|
||||
// moving a content type under another content type.
|
||||
|
||||
moveEventArgs.MoveInfoCollection = moveInfo;
|
||||
moveEventArgs.CanCancel = false;
|
||||
OnMoved(scope, moveEventArgs);
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Containers
|
||||
|
||||
protected abstract Guid ContainedObjectType { get; }
|
||||
|
||||
protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType);
|
||||
|
||||
public Attempt<OperationResult<OperationResultType, EntityContainer>> CreateContainer(int parentId, string name, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(WriteLockIds); // also for containers
|
||||
|
||||
try
|
||||
{
|
||||
var container = new EntityContainer(Constants.ObjectTypes.DocumentType)
|
||||
{
|
||||
Name = name,
|
||||
ParentId = parentId,
|
||||
CreatorId = userId
|
||||
};
|
||||
|
||||
var saveEventArgs = new SaveEventArgs<EntityContainer>(container, evtMsgs);
|
||||
if (OnSavingContainerCancelled(scope, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs, container);
|
||||
}
|
||||
|
||||
_containerRepository.Save(container);
|
||||
scope.Complete();
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
OnSavedContainer(scope, saveEventArgs);
|
||||
//TODO: Audit trail ?
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs, container);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Fail<OperationResultType, EntityContainer>(OperationResultType.FailedCancelledByEvent, evtMsgs, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<OperationResult> SaveContainer(EntityContainer container, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
var containerObjectType = ContainerObjectType;
|
||||
if (container.ContainerObjectType != containerObjectType)
|
||||
{
|
||||
var ex = new InvalidOperationException("Not a container of the proper type.");
|
||||
return OperationResult.Attempt.Fail(evtMsgs, ex);
|
||||
}
|
||||
|
||||
if (container.HasIdentity && container.IsPropertyDirty("ParentId"))
|
||||
{
|
||||
var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead.");
|
||||
return OperationResult.Attempt.Fail(evtMsgs, ex);
|
||||
}
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var args = new SaveEventArgs<EntityContainer>(container, evtMsgs);
|
||||
if (OnSavingContainerCancelled(scope, args))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
scope.WriteLock(WriteLockIds); // also for containers
|
||||
|
||||
_containerRepository.Save(container);
|
||||
scope.Complete();
|
||||
|
||||
args.CanCancel = false;
|
||||
OnSavedContainer(scope, args);
|
||||
}
|
||||
|
||||
//TODO: Audit trail ?
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
public EntityContainer GetContainer(int containerId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds); // also for containers
|
||||
|
||||
return _containerRepository.Get(containerId);
|
||||
}
|
||||
}
|
||||
|
||||
public EntityContainer GetContainer(Guid containerId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds); // also for containers
|
||||
|
||||
return ((EntityContainerRepository) _containerRepository).Get(containerId);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EntityContainer> GetContainers(int[] containerIds)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds); // also for containers
|
||||
|
||||
return _containerRepository.GetMany(containerIds);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EntityContainer> GetContainers(TItem item)
|
||||
{
|
||||
var ancestorIds = item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x =>
|
||||
{
|
||||
var asInt = x.TryConvertTo<int>();
|
||||
return asInt ? asInt.Result : int.MinValue;
|
||||
})
|
||||
.Where(x => x != int.MinValue && x != item.Id)
|
||||
.ToArray();
|
||||
|
||||
return GetContainers(ancestorIds);
|
||||
}
|
||||
|
||||
public IEnumerable<EntityContainer> GetContainers(string name, int level)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds); // also for containers
|
||||
|
||||
return ((EntityContainerRepository) _containerRepository).Get(name, level);
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<OperationResult> DeleteContainer(int containerId, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(WriteLockIds); // also for containers
|
||||
|
||||
var container = _containerRepository.Get(containerId);
|
||||
if (container == null) return OperationResult.Attempt.NoOperation(evtMsgs);
|
||||
|
||||
// 'container' here does not know about its children, so we need
|
||||
// to get it again from the entity repository, as a light entity
|
||||
var entity = _entityRepository.Get(container.Id);
|
||||
if (entity.HasChildren)
|
||||
{
|
||||
scope.Complete();
|
||||
return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs));
|
||||
}
|
||||
|
||||
var deleteEventArgs = new DeleteEventArgs<EntityContainer>(container, evtMsgs);
|
||||
if (OnDeletingContainerCancelled(scope, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs));
|
||||
}
|
||||
|
||||
_containerRepository.Delete(container);
|
||||
scope.Complete();
|
||||
|
||||
deleteEventArgs.CanCancel = false;
|
||||
OnDeletedContainer(scope, deleteEventArgs);
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
//TODO: Audit trail ?
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<OperationResult<OperationResultType, EntityContainer>> RenameContainer(int id, string name, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(WriteLockIds); // also for containers
|
||||
|
||||
try
|
||||
{
|
||||
var container = _containerRepository.Get(id);
|
||||
|
||||
//throw if null, this will be caught by the catch and a failed returned
|
||||
if (container == null)
|
||||
throw new InvalidOperationException("No container found with id " + id);
|
||||
|
||||
container.Name = name;
|
||||
|
||||
var saveEventArgs = new SaveEventArgs<EntityContainer>(container, evtMsgs);
|
||||
if (OnRenamingContainerCancelled(scope, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel<EntityContainer>(evtMsgs);
|
||||
}
|
||||
|
||||
_containerRepository.Save(container);
|
||||
scope.Complete();
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
OnRenamedContainer(scope, saveEventArgs);
|
||||
|
||||
return OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, container);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return OperationResult.Attempt.Fail<EntityContainer>(evtMsgs, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Audit
|
||||
|
||||
private void Audit(AuditType type, int userId, int objectId)
|
||||
{
|
||||
_auditRepository.Save(new AuditItem(objectId, type, userId,
|
||||
ObjectTypes.GetUmbracoObjectType(ContainedObjectType).GetName()));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,508 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the DataType Service, which is an easy access to operations involving <see cref="IDataType"/>
|
||||
/// </summary>
|
||||
internal class DataTypeService : ScopeRepositoryService, IDataTypeService
|
||||
{
|
||||
private readonly IDataTypeRepository _dataTypeRepository;
|
||||
private readonly IDataTypeContainerRepository _dataTypeContainerRepository;
|
||||
private readonly IContentTypeRepository _contentTypeRepository;
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
private readonly IEntityRepository _entityRepository;
|
||||
|
||||
public DataTypeService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IDataTypeRepository dataTypeRepository, IDataTypeContainerRepository dataTypeContainerRepository,
|
||||
IAuditRepository auditRepository, IEntityRepository entityRepository, IContentTypeRepository contentTypeRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_dataTypeRepository = dataTypeRepository;
|
||||
_dataTypeContainerRepository = dataTypeContainerRepository;
|
||||
_auditRepository = auditRepository;
|
||||
_entityRepository = entityRepository;
|
||||
_contentTypeRepository = contentTypeRepository;
|
||||
}
|
||||
|
||||
#region Containers
|
||||
|
||||
public Attempt<OperationResult<OperationResultType, EntityContainer>> CreateContainer(int parentId, string name, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
try
|
||||
{
|
||||
var container = new EntityContainer(Constants.ObjectTypes.DataType)
|
||||
{
|
||||
Name = name,
|
||||
ParentId = parentId,
|
||||
CreatorId = userId
|
||||
};
|
||||
|
||||
if (scope.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs<EntityContainer>(container, evtMsgs)))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs, container);
|
||||
}
|
||||
|
||||
_dataTypeContainerRepository.Save(container);
|
||||
scope.Complete();
|
||||
|
||||
scope.Events.Dispatch(SavedContainer, this, new SaveEventArgs<EntityContainer>(container, evtMsgs));
|
||||
//TODO: Audit trail ?
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs, container);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return OperationResult.Attempt.Fail<EntityContainer>(evtMsgs, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EntityContainer GetContainer(int containerId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _dataTypeContainerRepository.Get(containerId);
|
||||
}
|
||||
}
|
||||
|
||||
public EntityContainer GetContainer(Guid containerId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return ((EntityContainerRepository) _dataTypeContainerRepository).Get(containerId);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EntityContainer> GetContainers(string name, int level)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return ((EntityContainerRepository) _dataTypeContainerRepository).Get(name, level);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EntityContainer> GetContainers(IDataType dataType)
|
||||
{
|
||||
var ancestorIds = dataType.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x =>
|
||||
{
|
||||
var asInt = x.TryConvertTo<int>();
|
||||
return asInt ? asInt.Result : int.MinValue;
|
||||
})
|
||||
.Where(x => x != int.MinValue && x != dataType.Id)
|
||||
.ToArray();
|
||||
|
||||
return GetContainers(ancestorIds);
|
||||
}
|
||||
|
||||
public IEnumerable<EntityContainer> GetContainers(int[] containerIds)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _dataTypeContainerRepository.GetMany(containerIds);
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<OperationResult> SaveContainer(EntityContainer container, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
if (container.ContainedObjectType != Constants.ObjectTypes.DataType)
|
||||
{
|
||||
var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataType + " container.");
|
||||
return OperationResult.Attempt.Fail(evtMsgs, ex);
|
||||
}
|
||||
|
||||
if (container.HasIdentity && container.IsPropertyDirty("ParentId"))
|
||||
{
|
||||
var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead.");
|
||||
return OperationResult.Attempt.Fail(evtMsgs, ex);
|
||||
}
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
if (scope.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs<EntityContainer>(container, evtMsgs)))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
_dataTypeContainerRepository.Save(container);
|
||||
|
||||
scope.Events.Dispatch(SavedContainer, this, new SaveEventArgs<EntityContainer>(container, evtMsgs));
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
//TODO: Audit trail ?
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
public Attempt<OperationResult> DeleteContainer(int containerId, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var container = _dataTypeContainerRepository.Get(containerId);
|
||||
if (container == null) return OperationResult.Attempt.NoOperation(evtMsgs);
|
||||
|
||||
// 'container' here does not know about its children, so we need
|
||||
// to get it again from the entity repository, as a light entity
|
||||
var entity = _entityRepository.Get(container.Id);
|
||||
if (entity.HasChildren)
|
||||
{
|
||||
scope.Complete();
|
||||
return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs));
|
||||
}
|
||||
|
||||
if (scope.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs<EntityContainer>(container, evtMsgs)))
|
||||
{
|
||||
scope.Complete();
|
||||
return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs));
|
||||
}
|
||||
|
||||
_dataTypeContainerRepository.Delete(container);
|
||||
|
||||
scope.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs<EntityContainer>(container, evtMsgs));
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
//TODO: Audit trail ?
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
public Attempt<OperationResult<OperationResultType, EntityContainer>> RenameContainer(int id, string name, int userId = 0)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
try
|
||||
{
|
||||
var container = _dataTypeContainerRepository.Get(id);
|
||||
|
||||
//throw if null, this will be caught by the catch and a failed returned
|
||||
if (container == null)
|
||||
throw new InvalidOperationException("No container found with id " + id);
|
||||
|
||||
container.Name = name;
|
||||
|
||||
_dataTypeContainerRepository.Save(container);
|
||||
scope.Complete();
|
||||
|
||||
// fixme - triggering SavedContainer with a different name?!
|
||||
scope.Events.Dispatch(SavedContainer, this, new SaveEventArgs<EntityContainer>(container, evtMsgs), "RenamedContainer");
|
||||
|
||||
return OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, container);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return OperationResult.Attempt.Fail<EntityContainer>(evtMsgs, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDataType"/> by its Name
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the <see cref="IDataType"/></param>
|
||||
/// <returns><see cref="IDataType"/></returns>
|
||||
public IDataType GetDataType(string name)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _dataTypeRepository.Get(Query<IDataType>().Where(x => x.Name == name)).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDataType"/> by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="IDataType"/></param>
|
||||
/// <returns><see cref="IDataType"/></returns>
|
||||
public IDataType GetDataType(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _dataTypeRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDataType"/> by its unique guid Id
|
||||
/// </summary>
|
||||
/// <param name="id">Unique guid Id of the DataType</param>
|
||||
/// <returns><see cref="IDataType"/></returns>
|
||||
public IDataType GetDataType(Guid id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IDataType>().Where(x => x.Key == id);
|
||||
return _dataTypeRepository.Get(query).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDataType"/> by its control Id
|
||||
/// </summary>
|
||||
/// <param name="propertyEditorAlias">Alias of the property editor</param>
|
||||
/// <returns>Collection of <see cref="IDataType"/> objects with a matching contorl id</returns>
|
||||
public IEnumerable<IDataType> GetByEditorAlias(string propertyEditorAlias)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IDataType>().Where(x => x.EditorAlias == propertyEditorAlias);
|
||||
return _dataTypeRepository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="IDataType"/> objects or those with the ids passed in
|
||||
/// </summary>
|
||||
/// <param name="ids">Optional array of Ids</param>
|
||||
/// <returns>An enumerable list of <see cref="IDataType"/> objects</returns>
|
||||
public IEnumerable<IDataType> GetAll(params int[] ids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _dataTypeRepository.GetMany(ids);
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<OperationResult<MoveOperationStatusType>> Move(IDataType toMove, int parentId)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
var moveInfo = new List<MoveEventInfo<IDataType>>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var moveEventInfo = new MoveEventInfo<IDataType>(toMove, toMove.Path, parentId);
|
||||
var moveEventArgs = new MoveEventArgs<IDataType>(evtMsgs, moveEventInfo);
|
||||
if (scope.Events.DispatchCancelable(Moving, this, moveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
EntityContainer container = null;
|
||||
if (parentId > 0)
|
||||
{
|
||||
container = _dataTypeContainerRepository.Get(parentId);
|
||||
if (container == null)
|
||||
throw new DataOperationException<MoveOperationStatusType>(MoveOperationStatusType.FailedParentNotFound); // causes rollback
|
||||
}
|
||||
moveInfo.AddRange(_dataTypeRepository.Move(toMove, container));
|
||||
|
||||
moveEventArgs.MoveInfoCollection = moveInfo;
|
||||
moveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Moved, this, moveEventArgs);
|
||||
scope.Complete();
|
||||
}
|
||||
catch (DataOperationException<MoveOperationStatusType> ex)
|
||||
{
|
||||
scope.Complete(); // fixme what are we doing here exactly?
|
||||
return OperationResult.Attempt.Fail(ex.Operation, evtMsgs);
|
||||
}
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IDataType"/>
|
||||
/// </summary>
|
||||
/// <param name="dataType"><see cref="IDataType"/> to save</param>
|
||||
/// <param name="userId">Id of the user issueing the save</param>
|
||||
public void Save(IDataType dataType, int userId = 0)
|
||||
{
|
||||
dataType.CreatorId = userId;
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IDataType>(dataType);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dataType.Name))
|
||||
{
|
||||
throw new ArgumentException("Cannot save datatype with empty name.");
|
||||
}
|
||||
|
||||
_dataTypeRepository.Save(dataType);
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
Audit(AuditType.Save, userId, dataType.Id);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a collection of <see cref="IDataType"/>
|
||||
/// </summary>
|
||||
/// <param name="dataTypeDefinitions"><see cref="IDataType"/> to save</param>
|
||||
/// <param name="userId">Id of the user issueing the save</param>
|
||||
public void Save(IEnumerable<IDataType> dataTypeDefinitions, int userId = 0)
|
||||
{
|
||||
Save(dataTypeDefinitions, userId, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a collection of <see cref="IDataType"/>
|
||||
/// </summary>
|
||||
/// <param name="dataTypeDefinitions"><see cref="IDataType"/> to save</param>
|
||||
/// <param name="userId">Id of the user issueing the save</param>
|
||||
/// <param name="raiseEvents">Boolean indicating whether or not to raise events</param>
|
||||
public void Save(IEnumerable<IDataType> dataTypeDefinitions, int userId, bool raiseEvents)
|
||||
{
|
||||
var dataTypeDefinitionsA = dataTypeDefinitions.ToArray();
|
||||
var saveEventArgs = new SaveEventArgs<IDataType>(dataTypeDefinitionsA);
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var dataTypeDefinition in dataTypeDefinitionsA)
|
||||
{
|
||||
dataTypeDefinition.CreatorId = userId;
|
||||
_dataTypeRepository.Save(dataTypeDefinition);
|
||||
}
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
Audit(AuditType.Save, userId, -1);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an <see cref="IDataType"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Please note that deleting a <see cref="IDataType"/> will remove
|
||||
/// all the <see cref="PropertyType"/> data that references this <see cref="IDataType"/>.
|
||||
/// </remarks>
|
||||
/// <param name="dataType"><see cref="IDataType"/> to delete</param>
|
||||
/// <param name="userId">Optional Id of the user issueing the deletion</param>
|
||||
public void Delete(IDataType dataType, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<IDataType>(dataType);
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// find ContentTypes using this IDataTypeDefinition on a PropertyType, and delete
|
||||
// fixme - media and members?!
|
||||
// fixme - non-group properties?!
|
||||
var query = Query<PropertyType>().Where(x => x.DataTypeId == dataType.Id);
|
||||
var contentTypes = _contentTypeRepository.GetByQuery(query);
|
||||
foreach (var contentType in contentTypes)
|
||||
{
|
||||
foreach (var propertyGroup in contentType.PropertyGroups)
|
||||
{
|
||||
var types = propertyGroup.PropertyTypes.Where(x => x.DataTypeId == dataType.Id).ToList();
|
||||
foreach (var propertyType in types)
|
||||
{
|
||||
propertyGroup.PropertyTypes.Remove(propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
// so... we are modifying content types here. the service will trigger Deleted event,
|
||||
// which will propagate to DataTypeCacheRefresher which will clear almost every cache
|
||||
// there is to clear... and in addition published snapshot caches will clear themselves too, so
|
||||
// this is probably safe alghough it looks... weird.
|
||||
//
|
||||
// what IS weird is that a content type is losing a property and we do NOT raise any
|
||||
// content type event... so ppl better listen on the data type events too.
|
||||
|
||||
_contentTypeRepository.Save(contentType);
|
||||
}
|
||||
|
||||
_dataTypeRepository.Delete(dataType);
|
||||
|
||||
deleteEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Deleted, this, deleteEventArgs);
|
||||
Audit(AuditType.Delete, userId, dataType.Id);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private void Audit(AuditType type, int userId, int objectId)
|
||||
{
|
||||
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.DataType)));
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<EntityContainer>> SavingContainer;
|
||||
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<EntityContainer>> SavedContainer;
|
||||
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<EntityContainer>> DeletingContainer;
|
||||
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<EntityContainer>> DeletedContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<IDataType>> Deleting;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDataTypeService, DeleteEventArgs<IDataType>> Deleted;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<IDataType>> Saving;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDataTypeService, SaveEventArgs<IDataType>> Saved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Move
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDataTypeService, MoveEventArgs<IDataType>> Moving;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Move
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDataTypeService, MoveEventArgs<IDataType>> Moved;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public class DomainService : ScopeRepositoryService, IDomainService
|
||||
{
|
||||
private readonly IDomainRepository _domainRepository;
|
||||
|
||||
public DomainService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IDomainRepository domainRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_domainRepository = domainRepository;
|
||||
}
|
||||
|
||||
public bool Exists(string domainName)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _domainRepository.Exists(domainName);
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<OperationResult> Delete(IDomain domain)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<IDomain>(domain, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
_domainRepository.Delete(domain);
|
||||
scope.Complete();
|
||||
|
||||
deleteEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Deleted, this, deleteEventArgs);
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
public IDomain GetByName(string name)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _domainRepository.GetByName(name);
|
||||
}
|
||||
}
|
||||
|
||||
public IDomain GetById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _domainRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IDomain> GetAll(bool includeWildcards)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _domainRepository.GetAll(includeWildcards);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IDomain> GetAssignedDomains(int contentId, bool includeWildcards)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _domainRepository.GetAssignedDomains(contentId, includeWildcards);
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<OperationResult> Save(IDomain domainEntity)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IDomain>(domainEntity, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
_domainRepository.Save(domainEntity);
|
||||
scope.Complete();
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
/// <summary>
|
||||
/// Occurs before Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDomainService, DeleteEventArgs<IDomain>> Deleting;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDomainService, DeleteEventArgs<IDomain>> Deleted;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDomainService, SaveEventArgs<IDomain>> Saving;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IDomainService, SaveEventArgs<IDomain>> Saved;
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,637 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public class EntityService : ScopeRepositoryService, IEntityService
|
||||
{
|
||||
private readonly IEntityRepository _entityRepository;
|
||||
private readonly Dictionary<string, (UmbracoObjectTypes ObjectType, Func<int, IUmbracoEntity> GetById, Func<Guid, IUmbracoEntity> GetByKey)> _objectTypes;
|
||||
private IQuery<IUmbracoEntity> _queryRootEntity;
|
||||
private readonly IdkMap _idkMap;
|
||||
|
||||
public EntityService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IContentService contentService, IContentTypeService contentTypeService,
|
||||
IMediaService mediaService, IMediaTypeService mediaTypeService,
|
||||
IDataTypeService dataTypeService,
|
||||
IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap,
|
||||
IEntityRepository entityRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_idkMap = idkMap;
|
||||
_entityRepository = entityRepository;
|
||||
|
||||
_objectTypes = new Dictionary<string, (UmbracoObjectTypes, Func<int, IUmbracoEntity>, Func<Guid, IUmbracoEntity>)>
|
||||
{
|
||||
{ typeof (IDataType).FullName, (UmbracoObjectTypes.DataType, dataTypeService.GetDataType, dataTypeService.GetDataType) },
|
||||
{ typeof (IContent).FullName, (UmbracoObjectTypes.Document, contentService.GetById, contentService.GetById) },
|
||||
{ typeof (IContentType).FullName, (UmbracoObjectTypes.DocumentType, contentTypeService.Get, contentTypeService.Get) },
|
||||
{ typeof (IMedia).FullName, (UmbracoObjectTypes.Media, mediaService.GetById, mediaService.GetById) },
|
||||
{ typeof (IMediaType).FullName, (UmbracoObjectTypes.MediaType, mediaTypeService.Get, mediaTypeService.Get) },
|
||||
{ typeof (IMember).FullName, (UmbracoObjectTypes.Member, memberService.GetById, memberService.GetByKey) },
|
||||
{ typeof (IMemberType).FullName, (UmbracoObjectTypes.MemberType, memberTypeService.Get, memberTypeService.Get) },
|
||||
};
|
||||
}
|
||||
|
||||
#region Static Queries
|
||||
|
||||
// lazy-constructed because when the ctor runs, the query factory may not be ready
|
||||
private IQuery<IUmbracoEntity> QueryRootEntity => _queryRootEntity
|
||||
?? (_queryRootEntity = Query<IUmbracoEntity>().Where(x => x.ParentId == -1));
|
||||
|
||||
#endregion
|
||||
|
||||
// gets the getters, throws if not supported
|
||||
private (UmbracoObjectTypes ObjectType, Func<int, IUmbracoEntity> GetById, Func<Guid, IUmbracoEntity> GetByKey) GetGetters(Type type)
|
||||
{
|
||||
if (type?.FullName == null || !_objectTypes.TryGetValue(type.FullName, out var getters))
|
||||
throw new NotSupportedException($"Type \"{type?.FullName ?? "<null>"}\" is not supported here.");
|
||||
return getters;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEntitySlim Get(int id)
|
||||
{
|
||||
return (IEntitySlim) Get(id, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUmbracoEntity Get(int id, bool full)
|
||||
{
|
||||
if (!full)
|
||||
{
|
||||
// get the light entity
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
// get the full entity
|
||||
var objectType = GetObjectType(id);
|
||||
var entityType = objectType.GetClrType();
|
||||
var getters = GetGetters(entityType);
|
||||
return getters.GetById(id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEntitySlim Get(Guid key)
|
||||
{
|
||||
return (IEntitySlim) Get(key, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUmbracoEntity Get(Guid key, bool full)
|
||||
{
|
||||
if (!full)
|
||||
{
|
||||
// get the light entity
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.Get(key);
|
||||
}
|
||||
}
|
||||
|
||||
// get the full entity
|
||||
var objectType = GetObjectType(key);
|
||||
var entityType = objectType.GetClrType();
|
||||
var getters = GetGetters(entityType);
|
||||
return getters.GetByKey(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEntitySlim Get(int id, UmbracoObjectTypes objectType)
|
||||
{
|
||||
return (IEntitySlim) Get(id, objectType, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes objectType, bool full)
|
||||
{
|
||||
if (!full)
|
||||
{
|
||||
// get the light entity
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.Get(id, objectType.GetGuid());
|
||||
}
|
||||
}
|
||||
|
||||
// get the full entity
|
||||
var entityType = objectType.GetClrType();
|
||||
var getters = GetGetters(entityType);
|
||||
return getters.GetById(id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEntitySlim Get(Guid key, UmbracoObjectTypes objectType)
|
||||
{
|
||||
return (IEntitySlim) Get(key, objectType, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUmbracoEntity Get(Guid key, UmbracoObjectTypes objectType, bool full)
|
||||
{
|
||||
if (!full)
|
||||
{
|
||||
// get the light entity
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.Get(key, objectType.GetGuid());
|
||||
}
|
||||
}
|
||||
|
||||
// get the full entity
|
||||
var entityType = objectType.GetClrType();
|
||||
var getters = GetGetters(entityType);
|
||||
return getters.GetByKey(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEntitySlim Get<T>(int id)
|
||||
where T : IUmbracoEntity
|
||||
{
|
||||
return (IEntitySlim) Get<T>(id, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IUmbracoEntity Get<T>(int id, bool full)
|
||||
where T : IUmbracoEntity
|
||||
{
|
||||
if (!full)
|
||||
{
|
||||
// get the light entity
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
// get the full entity
|
||||
var entityType = typeof (T);
|
||||
var getters = GetGetters(entityType);
|
||||
return getters.GetById(id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEntitySlim Get<T>(Guid key)
|
||||
where T : IUmbracoEntity
|
||||
{
|
||||
return (IEntitySlim) Get<T>(key, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUmbracoEntity Get<T>(Guid key, bool full)
|
||||
where T : IUmbracoEntity
|
||||
{
|
||||
if (!full)
|
||||
{
|
||||
// get the light entity
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.Get(key);
|
||||
}
|
||||
}
|
||||
|
||||
// get the full entity
|
||||
var entityType = typeof (T);
|
||||
var getters = GetGetters(entityType);
|
||||
return getters.GetByKey(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Exists(int id)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.Exists(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Exists(Guid key)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.Exists(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetAll<T>() where T : IUmbracoEntity
|
||||
=> GetAll<T>(Array.Empty<int>());
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetAll<T>(params int[] ids)
|
||||
where T : IUmbracoEntity
|
||||
{
|
||||
var entityType = typeof (T);
|
||||
var getters = GetGetters(entityType);
|
||||
var objectType = getters.ObjectType;
|
||||
var objectTypeId = objectType.GetGuid();
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetAll(objectTypeId, ids);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetAll(UmbracoObjectTypes objectType)
|
||||
=> GetAll(objectType, Array.Empty<int>());
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetAll(UmbracoObjectTypes objectType, params int[] ids)
|
||||
{
|
||||
var entityType = objectType.GetClrType();
|
||||
GetGetters(entityType);
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetAll(objectType.GetGuid(), ids);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetAll(Guid objectType)
|
||||
=> GetAll(objectType, Array.Empty<int>());
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids)
|
||||
{
|
||||
var entityType = ObjectTypes.GetClrType(objectType);
|
||||
GetGetters(entityType);
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetAll(objectType, ids);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetAll<T>(params Guid[] keys)
|
||||
where T : IUmbracoEntity
|
||||
{
|
||||
var entityType = typeof (T);
|
||||
var getters = GetGetters(entityType);
|
||||
var objectType = getters.ObjectType;
|
||||
var objectTypeId = objectType.GetGuid();
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetAll(objectTypeId, keys);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntitySlim> GetAll(UmbracoObjectTypes objectType, Guid[] keys)
|
||||
{
|
||||
var entityType = objectType.GetClrType();
|
||||
GetGetters(entityType);
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetAll(objectType.GetGuid(), keys);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetAll(Guid objectType, params Guid[] keys)
|
||||
{
|
||||
var entityType = ObjectTypes.GetClrType(objectType);
|
||||
GetGetters(entityType);
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetAll(objectType, keys);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetRootEntities(UmbracoObjectTypes objectType)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetByQuery(QueryRootEntity, objectType.GetGuid());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEntitySlim GetParent(int id)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var entity = _entityRepository.Get(id);
|
||||
if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21)
|
||||
return null;
|
||||
return _entityRepository.Get(entity.ParentId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEntitySlim GetParent(int id, UmbracoObjectTypes objectType)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var entity = _entityRepository.Get(id);
|
||||
if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21)
|
||||
return null;
|
||||
return _entityRepository.Get(entity.ParentId, objectType.GetGuid());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetChildren(int parentId)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
|
||||
return _entityRepository.GetByQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetChildren(int parentId, UmbracoObjectTypes objectType)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
|
||||
return _entityRepository.GetByQuery(query, objectType.GetGuid());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the parent to retrieve children for</param>
|
||||
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/> objects</returns>
|
||||
internal IEnumerable<IEntitySlim> GetMediaChildrenWithoutPropertyData(int parentId)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == parentId);
|
||||
|
||||
//fixme - see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media
|
||||
return ((EntityRepository)_entityRepository).GetMediaByQueryWithoutPropertyData(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetDescendants(int id)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var entity = _entityRepository.Get(id);
|
||||
var pathMatch = entity.Path + ",";
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.Path.StartsWith(pathMatch) && x.Id != id);
|
||||
return _entityRepository.GetByQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IEntitySlim> GetDescendants(int id, UmbracoObjectTypes objectType)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var entity = _entityRepository.Get(id);
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.Path.StartsWith(entity.Path) && x.Id != id);
|
||||
return _entityRepository.GetByQuery(query, objectType.GetGuid());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntitySlim> GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
|
||||
string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "")
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IUmbracoEntity>().Where(x => x.ParentId == id && x.Trashed == false);
|
||||
|
||||
var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query<IUmbracoEntity>().Where(x => x.Name.Contains(filter));
|
||||
return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntitySlim> GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
|
||||
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var objectTypeGuid = objectType.GetGuid();
|
||||
var query = Query<IUmbracoEntity>();
|
||||
|
||||
if (id != Constants.System.Root)
|
||||
{
|
||||
// lookup the path so we can use it in the prefix query below
|
||||
var paths = _entityRepository.GetAllPaths(objectTypeGuid, id).ToArray();
|
||||
if (paths.Length == 0)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Enumerable.Empty<IEntitySlim>();
|
||||
}
|
||||
var path = paths[0].Path;
|
||||
query.Where(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar));
|
||||
}
|
||||
|
||||
var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query<IUmbracoEntity>().Where(x => x.Name.Contains(filter));
|
||||
return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntitySlim> GetPagedDescendants(IEnumerable<int> ids, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
|
||||
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
|
||||
{
|
||||
totalRecords = 0;
|
||||
|
||||
var idsA = ids.ToArray();
|
||||
if (idsA.Length == 0)
|
||||
return Enumerable.Empty<IEntitySlim>();
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var objectTypeGuid = objectType.GetGuid();
|
||||
var query = Query<IUmbracoEntity>();
|
||||
|
||||
if (idsA.All(x => x != Constants.System.Root))
|
||||
{
|
||||
var paths = _entityRepository.GetAllPaths(objectTypeGuid, idsA).ToArray();
|
||||
if (paths.Length == 0)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Enumerable.Empty<IEntitySlim>();
|
||||
}
|
||||
var clauses = new List<Expression<Func<IUmbracoEntity, bool>>>();
|
||||
foreach (var id in idsA)
|
||||
{
|
||||
// if the id is root then don't add any clauses
|
||||
if (id == Constants.System.Root) continue;
|
||||
|
||||
var entityPath = paths.FirstOrDefault(x => x.Id == id);
|
||||
if (entityPath == null) continue;
|
||||
|
||||
var path = entityPath.Path;
|
||||
var qid = id;
|
||||
clauses.Add(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar) || x.Path.SqlEndsWith("," + qid, TextColumnType.NVarchar));
|
||||
}
|
||||
query.WhereAny(clauses);
|
||||
}
|
||||
|
||||
var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query<IUmbracoEntity>().Where(x => x.Name.Contains(filter));
|
||||
return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntitySlim> GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
|
||||
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IUmbracoEntity>();
|
||||
if (includeTrashed == false)
|
||||
query.Where(x => x.Trashed == false);
|
||||
|
||||
var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query<IUmbracoEntity>().Where(x => x.Name.Contains(filter));
|
||||
return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual UmbracoObjectTypes GetObjectType(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var sql = scope.SqlContext.Sql()
|
||||
.Select<NodeDto>(x => x.NodeObjectType)
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeId == id);
|
||||
|
||||
var guid = scope.Database.ExecuteScalar<Guid>(sql);
|
||||
return ObjectTypes.GetUmbracoObjectType(guid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual UmbracoObjectTypes GetObjectType(Guid key)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var sql = scope.SqlContext.Sql()
|
||||
.Select<NodeDto>(x => x.NodeObjectType)
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.UniqueId == key);
|
||||
|
||||
var guid = scope.Database.ExecuteScalar<Guid>(sql);
|
||||
return ObjectTypes.GetUmbracoObjectType(guid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity)
|
||||
{
|
||||
return entity is IEntitySlim light
|
||||
? ObjectTypes.GetUmbracoObjectType(light.NodeObjectType)
|
||||
: GetObjectType(entity.Id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Type GetEntityType(int id)
|
||||
{
|
||||
var objectType = GetObjectType(id);
|
||||
return objectType.GetClrType();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Attempt<int> GetId(Guid key, UmbracoObjectTypes objectType)
|
||||
{
|
||||
return _idkMap.GetIdForKey(key, objectType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Attempt<int> GetId(Udi udi)
|
||||
{
|
||||
return _idkMap.GetIdForUdi(udi);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Attempt<Guid> GetKey(int id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
return _idkMap.GetKeyForId(id, umbracoObjectType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<TreeEntityPath> GetAllPaths(UmbracoObjectTypes objectType, params int[] ids)
|
||||
{
|
||||
var entityType = objectType.GetClrType();
|
||||
GetGetters(entityType);
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetAllPaths(objectType.GetGuid(), ids);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<TreeEntityPath> GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys)
|
||||
{
|
||||
var entityType = objectType.GetClrType();
|
||||
GetGetters(entityType);
|
||||
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _entityRepository.GetAllPaths(objectType.GetGuid(), keys);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int ReserveId(Guid key)
|
||||
{
|
||||
NodeDto node;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var sql = scope.SqlContext.Sql()
|
||||
.Select<NodeDto>()
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.UniqueId == key && x.NodeObjectType == Constants.ObjectTypes.IdReservation);
|
||||
|
||||
node = scope.Database.SingleOrDefault<NodeDto>(sql);
|
||||
if (node != null)
|
||||
throw new InvalidOperationException("An identifier has already been reserved for this Udi.");
|
||||
|
||||
node = new NodeDto
|
||||
{
|
||||
UniqueId = key,
|
||||
Text = "RESERVED.ID",
|
||||
NodeObjectType = Constants.ObjectTypes.IdReservation,
|
||||
|
||||
CreateDate = DateTime.Now,
|
||||
UserId = null,
|
||||
ParentId = -1,
|
||||
Level = 1,
|
||||
Path = "-1",
|
||||
SortOrder = 0,
|
||||
Trashed = false
|
||||
};
|
||||
scope.Database.Insert(node);
|
||||
scope.Complete();
|
||||
}
|
||||
return node.NodeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public class ExternalLoginService : ScopeRepositoryService, IExternalLoginService
|
||||
{
|
||||
private readonly IExternalLoginRepository _externalLoginRepository;
|
||||
|
||||
public ExternalLoginService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IExternalLoginRepository externalLoginRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_externalLoginRepository = externalLoginRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all user logins assigned
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IIdentityUserLogin> GetAll(int userId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _externalLoginRepository.Get(Query<IIdentityUserLogin>().Where(x => x.UserId == userId))
|
||||
.ToList(); // ToList is important here, must evaluate within uow! // ToList is important here, must evaluate within uow!
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all logins matching the login info - generally there should only be one but in some cases
|
||||
/// there might be more than one depending on if an adminstrator has been editing/removing members
|
||||
/// </summary>
|
||||
/// <param name="login"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IIdentityUserLogin> Find(UserLoginInfo login)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _externalLoginRepository.Get(Query<IIdentityUserLogin>().Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider))
|
||||
.ToList(); // ToList is important here, must evaluate within uow! // ToList is important here, must evaluate within uow!
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save user logins
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="logins"></param>
|
||||
public void SaveUserLogins(int userId, IEnumerable<UserLoginInfo> logins)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_externalLoginRepository.SaveUserLogins(userId, logins);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all user logins - normally used when a member is deleted
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
public void DeleteUserLogins(int userId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_externalLoginRepository.DeleteUserLogins(userId);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,210 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
internal class KeyValueService : IKeyValueService
|
||||
{
|
||||
private readonly object _initialock = new object();
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly ILogger _logger;
|
||||
private bool _initialized;
|
||||
|
||||
public KeyValueService(IScopeProvider scopeProvider, ILogger logger)
|
||||
{
|
||||
_scopeProvider = scopeProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
lock (_initialock)
|
||||
{
|
||||
if (_initialized) return;
|
||||
Initialize();
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
// the key/value service is entirely self-managed, because it is used by the
|
||||
// upgrader and anything we might change need to happen before everything else
|
||||
|
||||
// if already running 8, either following an upgrade or an install,
|
||||
// then everything should be ok (the table should exist, etc)
|
||||
|
||||
if (UmbracoVersion.LocalVersion.Major >= 8)
|
||||
return;
|
||||
|
||||
// else we are upgrading from 7, we can assume that the locks table
|
||||
// exists, but we need to create everything for key/value
|
||||
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
var context = new MigrationContext(scope.Database, _logger);
|
||||
var initMigration = new InitializeMigration(context);
|
||||
initMigration.Migrate();
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom migration that executes standalone during the Initialize phase of this service.
|
||||
/// </summary>
|
||||
internal class InitializeMigration : MigrationBase
|
||||
{
|
||||
public InitializeMigration(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
// as long as we are still running 7 this migration will be invoked,
|
||||
// but due to multiple restarts during upgrades, maybe the table
|
||||
// exists already
|
||||
if (TableExists(Constants.DatabaseSchema.Tables.KeyValue))
|
||||
return;
|
||||
|
||||
Logger.Info<KeyValueService>("Creating KeyValue structure.");
|
||||
|
||||
// the locks table was initially created with an identity (auto-increment) primary key,
|
||||
// but we don't want this, especially as we are about to insert a new row into the table,
|
||||
// so here we drop that identity
|
||||
DropLockTableIdentity();
|
||||
|
||||
// insert the lock object for key/value
|
||||
Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do();
|
||||
|
||||
// create the key-value table
|
||||
Create.Table<KeyValueDto>().Do();
|
||||
}
|
||||
|
||||
private void DropLockTableIdentity()
|
||||
{
|
||||
// one cannot simply drop an identity, that requires a bit of work
|
||||
|
||||
// create a temp. id column and copy values
|
||||
Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do();
|
||||
Execute.Sql("update umbracoLock set nid = id").Do();
|
||||
|
||||
// drop the id column entirely (cannot just drop identity)
|
||||
Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do();
|
||||
Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do();
|
||||
|
||||
// recreate the id column without identity and copy values
|
||||
Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do();
|
||||
Execute.Sql("update umbracoLock set id = nid").Do();
|
||||
|
||||
// drop the temp. id column
|
||||
Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do();
|
||||
|
||||
// complete the primary key
|
||||
Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetValue(string key)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
var sql = scope.SqlContext.Sql().Select<KeyValueDto>().From<KeyValueDto>().Where<KeyValueDto>(x => x.Key == key);
|
||||
var dto = scope.Database.Fetch<KeyValueDto>(sql).FirstOrDefault();
|
||||
scope.Complete();
|
||||
return dto?.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValue(string key, string value)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.KeyValues);
|
||||
|
||||
var sql = scope.SqlContext.Sql().Select<KeyValueDto>().From<KeyValueDto>().Where<KeyValueDto>(x => x.Key == key);
|
||||
var dto = scope.Database.Fetch<KeyValueDto>(sql).FirstOrDefault();
|
||||
|
||||
if (dto == null)
|
||||
{
|
||||
dto = new KeyValueDto
|
||||
{
|
||||
Key = key,
|
||||
Value = value,
|
||||
Updated = DateTime.Now
|
||||
};
|
||||
|
||||
scope.Database.Insert(dto);
|
||||
}
|
||||
else
|
||||
{
|
||||
dto.Value = value;
|
||||
dto.Updated = DateTime.Now;
|
||||
scope.Database.Update(dto);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValue(string key, string originValue, string newValue)
|
||||
{
|
||||
if (!TrySetValue(key, originValue, newValue))
|
||||
throw new InvalidOperationException("Could not set the value.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TrySetValue(string key, string originValue, string newValue)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.KeyValues);
|
||||
|
||||
var sql = scope.SqlContext.Sql().Select<KeyValueDto>().From<KeyValueDto>().Where<KeyValueDto>(x => x.Key == key);
|
||||
var dto = scope.Database.Fetch<KeyValueDto>(sql).FirstOrDefault();
|
||||
|
||||
if (dto == null || dto.Value != originValue)
|
||||
return false;
|
||||
|
||||
dto.Value = newValue;
|
||||
dto.Updated = DateTime.Now;
|
||||
scope.Database.Update(dto);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value directly from the database, no scope, nothing.
|
||||
/// </summary>
|
||||
/// <remarks>Used by <see cref="Runtime.CoreRuntime"/> to determine the runtime state.</remarks>
|
||||
internal static string GetValue(IUmbracoDatabase database, string key)
|
||||
{
|
||||
// not 8 yet = no key/value table, no value
|
||||
if (UmbracoVersion.LocalVersion.Major < 8)
|
||||
return null;
|
||||
|
||||
var sql = database.SqlContext.Sql()
|
||||
.Select<KeyValueDto>()
|
||||
.From<KeyValueDto>()
|
||||
.Where<KeyValueDto>(x => x.Key == key);
|
||||
return database.FirstOrDefault<KeyValueDto>(sql)?.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,508 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Localization Service, which is an easy access to operations involving <see cref="Language"/> and <see cref="DictionaryItem"/>
|
||||
/// </summary>
|
||||
public class LocalizationService : ScopeRepositoryService, ILocalizationService
|
||||
{
|
||||
private readonly IDictionaryRepository _dictionaryRepository;
|
||||
private readonly ILanguageRepository _languageRepository;
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
|
||||
public LocalizationService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IDictionaryRepository dictionaryRepository, IAuditRepository auditRepository, ILanguageRepository languageRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_dictionaryRepository = dictionaryRepository;
|
||||
_auditRepository = auditRepository;
|
||||
_languageRepository = languageRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates a translation for a dictionary item and language
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="language"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This does not save the item, that needs to be done explicitly
|
||||
/// </remarks>
|
||||
public void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage language, string value)
|
||||
{
|
||||
if (item == null) throw new ArgumentNullException(nameof(item));
|
||||
if (language == null) throw new ArgumentNullException(nameof(language));
|
||||
|
||||
var existing = item.Translations.FirstOrDefault(x => x.Language.Id == language.Id);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Translations = new List<IDictionaryTranslation>(item.Translations)
|
||||
{
|
||||
new DictionaryTranslation(language, value)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and saves a new dictionary item and assigns a value to all languages if defaultValue is specified.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="parentId"></param>
|
||||
/// <param name="defaultValue"></param>
|
||||
/// <returns></returns>
|
||||
public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string defaultValue = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
//validate the parent
|
||||
|
||||
if (parentId.HasValue && parentId.Value != Guid.Empty)
|
||||
{
|
||||
var parent = GetDictionaryItemById(parentId.Value);
|
||||
if (parent == null)
|
||||
throw new ArgumentException($"No parent dictionary item was found with id {parentId.Value}.");
|
||||
}
|
||||
|
||||
var item = new DictionaryItem(parentId, key);
|
||||
|
||||
if (defaultValue.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
var langs = GetAllLanguages();
|
||||
var translations = langs.Select(language => new DictionaryTranslation(language, defaultValue))
|
||||
.Cast<IDictionaryTranslation>()
|
||||
.ToList();
|
||||
|
||||
item.Translations = translations;
|
||||
}
|
||||
var saveEventArgs = new SaveEventArgs<IDictionaryItem>(item);
|
||||
|
||||
if (scope.Events.DispatchCancelable(SavingDictionaryItem, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return item;
|
||||
}
|
||||
_dictionaryRepository.Save(item);
|
||||
|
||||
// ensure the lazy Language callback is assigned
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(SavedDictionaryItem, this, saveEventArgs);
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDictionaryItem"/> by its <see cref="Int32"/> id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="IDictionaryItem"/></param>
|
||||
/// <returns><see cref="IDictionaryItem"/></returns>
|
||||
public IDictionaryItem GetDictionaryItemById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var item = _dictionaryRepository.Get(id);
|
||||
//ensure the lazy Language callback is assigned
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDictionaryItem"/> by its <see cref="Guid"/> id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="IDictionaryItem"/></param>
|
||||
/// <returns><see cref="DictionaryItem"/></returns>
|
||||
public IDictionaryItem GetDictionaryItemById(Guid id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var item = _dictionaryRepository.Get(id);
|
||||
//ensure the lazy Language callback is assigned
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDictionaryItem"/> by its key
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the <see cref="IDictionaryItem"/></param>
|
||||
/// <returns><see cref="IDictionaryItem"/></returns>
|
||||
public IDictionaryItem GetDictionaryItemByKey(string key)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var item = _dictionaryRepository.Get(key);
|
||||
//ensure the lazy Language callback is assigned
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of children for a <see cref="IDictionaryItem"/>
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the parent</param>
|
||||
/// <returns>An enumerable list of <see cref="IDictionaryItem"/> objects</returns>
|
||||
public IEnumerable<IDictionaryItem> GetDictionaryItemChildren(Guid parentId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IDictionaryItem>().Where(x => x.ParentId == parentId);
|
||||
var items = _dictionaryRepository.Get(query).ToArray();
|
||||
//ensure the lazy Language callback is assigned
|
||||
foreach (var item in items)
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of descendants for a <see cref="IDictionaryItem"/>
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the parent, null will return all dictionary items</param>
|
||||
/// <returns>An enumerable list of <see cref="IDictionaryItem"/> objects</returns>
|
||||
public IEnumerable<IDictionaryItem> GetDictionaryItemDescendants(Guid? parentId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var items = _dictionaryRepository.GetDictionaryItemDescendants(parentId).ToArray();
|
||||
//ensure the lazy Language callback is assigned
|
||||
foreach (var item in items)
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root/top <see cref="IDictionaryItem"/> objects
|
||||
/// </summary>
|
||||
/// <returns>An enumerable list of <see cref="IDictionaryItem"/> objects</returns>
|
||||
public IEnumerable<IDictionaryItem> GetRootDictionaryItems()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IDictionaryItem>().Where(x => x.ParentId == null);
|
||||
var items = _dictionaryRepository.Get(query).ToArray();
|
||||
//ensure the lazy Language callback is assigned
|
||||
foreach (var item in items)
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a <see cref="IDictionaryItem"/> with given key exists
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the <see cref="IDictionaryItem"/></param>
|
||||
/// <returns>True if a <see cref="IDictionaryItem"/> exists, otherwise false</returns>
|
||||
public bool DictionaryItemExists(string key)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var item = _dictionaryRepository.Get(key);
|
||||
return item != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a <see cref="IDictionaryItem"/> object
|
||||
/// </summary>
|
||||
/// <param name="dictionaryItem"><see cref="IDictionaryItem"/> to save</param>
|
||||
/// <param name="userId">Optional id of the user saving the dictionary item</param>
|
||||
public void Save(IDictionaryItem dictionaryItem, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
if (scope.Events.DispatchCancelable(SavingDictionaryItem, this, new SaveEventArgs<IDictionaryItem>(dictionaryItem)))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_dictionaryRepository.Save(dictionaryItem);
|
||||
|
||||
// ensure the lazy Language callback is assigned
|
||||
// ensure the lazy Language callback is assigned
|
||||
|
||||
EnsureDictionaryItemLanguageCallback(dictionaryItem);
|
||||
scope.Events.Dispatch(SavedDictionaryItem, this, new SaveEventArgs<IDictionaryItem>(dictionaryItem, false));
|
||||
|
||||
Audit(AuditType.Save, "Save DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem");
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <see cref="IDictionaryItem"/> object and its related translations
|
||||
/// as well as its children.
|
||||
/// </summary>
|
||||
/// <param name="dictionaryItem"><see cref="IDictionaryItem"/> to delete</param>
|
||||
/// <param name="userId">Optional id of the user deleting the dictionary item</param>
|
||||
public void Delete(IDictionaryItem dictionaryItem, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<IDictionaryItem>(dictionaryItem);
|
||||
if (scope.Events.DispatchCancelable(DeletingDictionaryItem, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_dictionaryRepository.Delete(dictionaryItem);
|
||||
deleteEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(DeletedDictionaryItem, this, deleteEventArgs);
|
||||
|
||||
Audit(AuditType.Delete, "Delete DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem");
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Language"/> by its id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="Language"/></param>
|
||||
/// <returns><see cref="Language"/></returns>
|
||||
public ILanguage GetLanguageById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _languageRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Language"/> by its iso code
|
||||
/// </summary>
|
||||
/// <param name="isoCode">Iso Code of the language (ie. en-US)</param>
|
||||
/// <returns><see cref="Language"/></returns>
|
||||
public ILanguage GetLanguageByIsoCode(string isoCode)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _languageRepository.GetByIsoCode(isoCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? GetLanguageIdByIsoCode(string isoCode)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _languageRepository.GetIdByIsoCode(isoCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetLanguageIsoCodeById(int id)
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _languageRepository.GetIsoCodeById(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetDefaultLanguageIsoCode()
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _languageRepository.GetDefaultIsoCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? GetDefaultLanguageId()
|
||||
{
|
||||
using (ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _languageRepository.GetDefaultId();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available languages
|
||||
/// </summary>
|
||||
/// <returns>An enumerable list of <see cref="ILanguage"/> objects</returns>
|
||||
public IEnumerable<ILanguage> GetAllLanguages()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _languageRepository.GetMany();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a <see cref="ILanguage"/> object
|
||||
/// </summary>
|
||||
/// <param name="language"><see cref="ILanguage"/> to save</param>
|
||||
/// <param name="userId">Optional id of the user saving the language</param>
|
||||
public void Save(ILanguage language, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
// write-lock languages to guard against race conds when dealing with default language
|
||||
scope.WriteLock(Constants.Locks.Languages);
|
||||
|
||||
// look for cycles - within write-lock
|
||||
if (language.FallbackLanguageId.HasValue)
|
||||
{
|
||||
var languages = _languageRepository.GetMany().ToDictionary(x => x.Id, x => x);
|
||||
if (!languages.ContainsKey(language.FallbackLanguageId.Value))
|
||||
throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback id={language.FallbackLanguageId.Value} which is not a valid language id.");
|
||||
if (CreatesCycle(language, languages))
|
||||
throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback {languages[language.FallbackLanguageId.Value].IsoCode} as it would create a fallback cycle.");
|
||||
}
|
||||
|
||||
var saveEventArgs = new SaveEventArgs<ILanguage>(language);
|
||||
if (scope.Events.DispatchCancelable(SavingLanguage, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_languageRepository.Save(language);
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(SavedLanguage, this, saveEventArgs);
|
||||
|
||||
Audit(AuditType.Save, "Save Language", userId, language.Id, ObjectTypes.GetName(UmbracoObjectTypes.Language));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreatesCycle(ILanguage language, IDictionary<int, ILanguage> languages)
|
||||
{
|
||||
// a new language is not referenced yet, so cannot be part of a cycle
|
||||
if (!language.HasIdentity) return false;
|
||||
|
||||
var id = language.FallbackLanguageId;
|
||||
while (true) // assuming languages does not already contains a cycle, this must end
|
||||
{
|
||||
if (!id.HasValue) return false; // no fallback means no cycle
|
||||
if (id.Value == language.Id) return true; // back to language = cycle!
|
||||
id = languages[id.Value].FallbackLanguageId; // else keep chaining
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <see cref="ILanguage"/> by removing it (but not its usages) from the db
|
||||
/// </summary>
|
||||
/// <param name="language"><see cref="ILanguage"/> to delete</param>
|
||||
/// <param name="userId">Optional id of the user deleting the language</param>
|
||||
public void Delete(ILanguage language, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
// write-lock languages to guard against race conds when dealing with default language
|
||||
scope.WriteLock(Constants.Locks.Languages);
|
||||
|
||||
var deleteEventArgs = new DeleteEventArgs<ILanguage>(language);
|
||||
if (scope.Events.DispatchCancelable(DeletingLanguage, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted
|
||||
_languageRepository.Delete(language);
|
||||
deleteEventArgs.CanCancel = false;
|
||||
|
||||
scope.Events.Dispatch(DeletedLanguage, this, deleteEventArgs);
|
||||
|
||||
Audit(AuditType.Delete, "Delete Language", userId, language.Id, ObjectTypes.GetName(UmbracoObjectTypes.Language));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private void Audit(AuditType type, string message, int userId, int objectId, string entityType)
|
||||
{
|
||||
_auditRepository.Save(new AuditItem(objectId, type, userId, entityType, message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is here to take care of a hack - the DictionaryTranslation model contains an ILanguage reference which we don't want but
|
||||
/// we cannot remove it because it would be a large breaking change, so we need to make sure it's resolved lazily. This is because
|
||||
/// if developers have a lot of dictionary items and translations, the caching and cloning size gets much much larger because of
|
||||
/// the large object graphs. So now we don't cache or clone the attached ILanguage
|
||||
/// </summary>
|
||||
private void EnsureDictionaryItemLanguageCallback(IDictionaryItem d)
|
||||
{
|
||||
var item = d as DictionaryItem;
|
||||
if (item == null) return;
|
||||
|
||||
item.GetLanguage = GetLanguageById;
|
||||
foreach (var trans in item.Translations.OfType<DictionaryTranslation>())
|
||||
trans.GetLanguage = GetLanguageById;
|
||||
}
|
||||
|
||||
public Dictionary<string, Guid> GetDictionaryItemKeyMap()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _dictionaryRepository.GetDictionaryItemKeyMap();
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
/// <summary>
|
||||
/// Occurs before Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<ILocalizationService, DeleteEventArgs<ILanguage>> DeletingLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<ILocalizationService, DeleteEventArgs<ILanguage>> DeletedLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<ILocalizationService, DeleteEventArgs<IDictionaryItem>> DeletingDictionaryItem;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<ILocalizationService, DeleteEventArgs<IDictionaryItem>> DeletedDictionaryItem;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<ILocalizationService, SaveEventArgs<IDictionaryItem>> SavingDictionaryItem;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<ILocalizationService, SaveEventArgs<IDictionaryItem>> SavedDictionaryItem;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<ILocalizationService, SaveEventArgs<ILanguage>> SavingLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<ILocalizationService, SaveEventArgs<ILanguage>> SavedLanguage;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
//TODO: Convert all of this over to Niels K's localization framework one day
|
||||
|
||||
public class LocalizedTextService : ILocalizedTextService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Lazy<LocalizedTextServiceFileSources> _fileSources;
|
||||
private readonly IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> _dictionarySource;
|
||||
private readonly IDictionary<CultureInfo, Lazy<XDocument>> _xmlSource;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with a file sources instance
|
||||
/// </summary>
|
||||
/// <param name="fileSources"></param>
|
||||
/// <param name="logger"></param>
|
||||
public LocalizedTextService(Lazy<LocalizedTextServiceFileSources> fileSources, ILogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
_logger = logger;
|
||||
if (fileSources == null) throw new ArgumentNullException("fileSources");
|
||||
_fileSources = fileSources;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with an XML source
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="logger"></param>
|
||||
public LocalizedTextService(IDictionary<CultureInfo, Lazy<XDocument>> source, ILogger logger)
|
||||
{
|
||||
if (source == null) throw new ArgumentNullException("source");
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
_xmlSource = source;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes with a source of a dictionary of culture -> areas -> sub dictionary of keys/values
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="logger"></param>
|
||||
public LocalizedTextService(IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> source, ILogger logger)
|
||||
{
|
||||
_dictionarySource = source ?? throw new ArgumentNullException(nameof(source));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public string Localize(string key, CultureInfo culture, IDictionary<string, string> tokens = null)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException(nameof(culture));
|
||||
|
||||
//TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
|
||||
culture = ConvertToSupportedCultureWithRegionCode(culture);
|
||||
|
||||
//This is what the legacy ui service did
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return string.Empty;
|
||||
|
||||
var keyParts = key.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
var area = keyParts.Length > 1 ? keyParts[0] : null;
|
||||
var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0];
|
||||
|
||||
var xmlSource = _xmlSource ?? (_fileSources != null
|
||||
? _fileSources.Value.GetXmlSources()
|
||||
: null);
|
||||
|
||||
if (xmlSource != null)
|
||||
{
|
||||
return GetFromXmlSource(xmlSource, culture, area, alias, tokens);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetFromDictionarySource(culture, area, alias, tokens);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all key/values in storage for the given culture
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IDictionary<string, string> GetAllStoredValues(CultureInfo culture)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException("culture");
|
||||
|
||||
//TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
|
||||
culture = ConvertToSupportedCultureWithRegionCode(culture);
|
||||
|
||||
var result = new Dictionary<string, string>();
|
||||
|
||||
var xmlSource = _xmlSource ?? (_fileSources != null
|
||||
? _fileSources.Value.GetXmlSources()
|
||||
: null);
|
||||
|
||||
if (xmlSource != null)
|
||||
{
|
||||
if (xmlSource.ContainsKey(culture) == false)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return result;
|
||||
}
|
||||
|
||||
//convert all areas + keys to a single key with a '/'
|
||||
result = GetStoredTranslations(xmlSource, culture);
|
||||
|
||||
//merge with the english file in case there's keys in there that don't exist in the local file
|
||||
var englishCulture = new CultureInfo("en-US");
|
||||
if (culture.Equals(englishCulture) == false)
|
||||
{
|
||||
var englishResults = GetStoredTranslations(xmlSource, englishCulture);
|
||||
foreach (var englishResult in englishResults.Where(englishResult => result.ContainsKey(englishResult.Key) == false))
|
||||
result.Add(englishResult.Key, englishResult.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_dictionarySource.ContainsKey(culture) == false)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return result;
|
||||
}
|
||||
|
||||
//convert all areas + keys to a single key with a '/'
|
||||
foreach (var area in _dictionarySource[culture])
|
||||
{
|
||||
foreach (var key in area.Value)
|
||||
{
|
||||
var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key);
|
||||
//i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case.
|
||||
if (result.ContainsKey(dictionaryKey) == false)
|
||||
{
|
||||
result.Add(dictionaryKey, key.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetStoredTranslations(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo cult)
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
var areas = xmlSource[cult].Value.XPathSelectElements("//area");
|
||||
foreach (var area in areas)
|
||||
{
|
||||
var keys = area.XPathSelectElements("./key");
|
||||
foreach (var key in keys)
|
||||
{
|
||||
var dictionaryKey = string.Format("{0}/{1}", (string)area.Attribute("alias"),
|
||||
(string)key.Attribute("alias"));
|
||||
//there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
|
||||
if (result.ContainsKey(dictionaryKey) == false)
|
||||
result.Add(dictionaryKey, key.Value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all currently supported cultures
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<CultureInfo> GetSupportedCultures()
|
||||
{
|
||||
var xmlSource = _xmlSource ?? (_fileSources != null
|
||||
? _fileSources.Value.GetXmlSources()
|
||||
: null);
|
||||
|
||||
return xmlSource != null ? xmlSource.Keys : _dictionarySource.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to resolve a full 4 letter culture from a 2 letter culture name
|
||||
/// </summary>
|
||||
/// <param name="currentCulture">
|
||||
/// The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be returned
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since that
|
||||
/// is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this attempts
|
||||
/// to resolve the full culture if possible.
|
||||
///
|
||||
/// This only works when this service is constructed with the LocalizedTextServiceFileSources
|
||||
/// </remarks>
|
||||
public CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture)
|
||||
{
|
||||
if (currentCulture == null) throw new ArgumentNullException("currentCulture");
|
||||
|
||||
if (_fileSources == null) return currentCulture;
|
||||
if (currentCulture.Name.Length > 2) return currentCulture;
|
||||
|
||||
var attempt = _fileSources.Value.TryConvert2LetterCultureTo4Letter(currentCulture.TwoLetterISOLanguageName);
|
||||
return attempt ? attempt.Result : currentCulture;
|
||||
}
|
||||
|
||||
private string GetFromDictionarySource(CultureInfo culture, string area, string key, IDictionary<string, string> tokens)
|
||||
{
|
||||
if (_dictionarySource.ContainsKey(culture) == false)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return "[" + key + "]";
|
||||
}
|
||||
|
||||
var cultureSource = _dictionarySource[culture];
|
||||
|
||||
string found;
|
||||
if (area.IsNullOrWhiteSpace())
|
||||
{
|
||||
found = cultureSource
|
||||
.SelectMany(x => x.Value)
|
||||
.Where(keyvals => keyvals.Key.InvariantEquals(key))
|
||||
.Select(x => x.Value)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
found = cultureSource
|
||||
.Where(areas => areas.Key.InvariantEquals(area))
|
||||
.SelectMany(a => a.Value)
|
||||
.Where(keyvals => keyvals.Key.InvariantEquals(key))
|
||||
.Select(x => x.Value)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
return ParseTokens(found, tokens);
|
||||
}
|
||||
|
||||
//NOTE: Based on how legacy works, the default text does not contain the area, just the key
|
||||
return "[" + key + "]";
|
||||
}
|
||||
|
||||
private string GetFromXmlSource(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo culture, string area, string key, IDictionary<string, string> tokens)
|
||||
{
|
||||
if (xmlSource.ContainsKey(culture) == false)
|
||||
{
|
||||
_logger.Warn<LocalizedTextService>("The culture specified {Culture} was not found in any configured sources for this service", culture);
|
||||
return "[" + key + "]";
|
||||
}
|
||||
|
||||
var found = FindTranslation(xmlSource, culture, area, key);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
return ParseTokens(found.Value, tokens);
|
||||
}
|
||||
|
||||
// Fall back to English by default if we can't find the key
|
||||
found = FindTranslation(xmlSource, new CultureInfo("en-US"), area, key);
|
||||
if (found != null)
|
||||
return ParseTokens(found.Value, tokens);
|
||||
|
||||
// If it can't be found in either file, fall back to the default, showing just the key in square brackets
|
||||
// NOTE: Based on how legacy works, the default text does not contain the area, just the key
|
||||
return "[" + key + "]";
|
||||
}
|
||||
|
||||
private XElement FindTranslation(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo culture, string area, string key)
|
||||
{
|
||||
var cultureSource = xmlSource[culture].Value;
|
||||
|
||||
var xpath = area.IsNullOrWhiteSpace()
|
||||
? string.Format("//key [@alias = '{0}']", key)
|
||||
: string.Format("//area [@alias = '{0}']/key [@alias = '{1}']", area, key);
|
||||
|
||||
var found = cultureSource.XPathSelectElement(xpath);
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the tokens in the value
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="tokens"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is based on how the legacy ui localized text worked, each token was just a sequential value delimited with a % symbol.
|
||||
/// For example: hello %0%, you are %1% !
|
||||
///
|
||||
/// Since we're going to continue using the same language files for now, the token system needs to remain the same. With our new service
|
||||
/// we support a dictionary which means in the future we can really have any sort of token system.
|
||||
/// Currently though, the token key's will need to be an integer and sequential - though we aren't going to throw exceptions if that is not the case.
|
||||
/// </remarks>
|
||||
internal static string ParseTokens(string value, IDictionary<string, string> tokens)
|
||||
{
|
||||
if (tokens == null || tokens.Any() == false)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
value = value.Replace(string.Format("{0}{1}{0}", "%", token.Key), token.Value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes the XDocument sources from files for the default localization text service and ensure caching is taken care of
|
||||
/// </summary>
|
||||
public class LocalizedTextServiceFileSources
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IRuntimeCacheProvider _cache;
|
||||
private readonly IEnumerable<LocalizedTextServiceSupplementaryFileSource> _supplementFileSources;
|
||||
private readonly DirectoryInfo _fileSourceFolder;
|
||||
|
||||
//TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
|
||||
private readonly Dictionary<string, CultureInfo> _twoLetterCultureConverter = new Dictionary<string, CultureInfo>();
|
||||
|
||||
private readonly Lazy<Dictionary<CultureInfo, Lazy<XDocument>>> _xmlSources;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to configure the file sources with the main file sources shipped with Umbraco and also including supplemental/plugin based
|
||||
/// localization files. The supplemental files will be loaded in and merged in after the primary files.
|
||||
/// The supplemental files must be named with the 4 letter culture name with a hyphen such as : en-AU.xml
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="fileSourceFolder"></param>
|
||||
/// <param name="supplementFileSources"></param>
|
||||
public LocalizedTextServiceFileSources(
|
||||
ILogger logger,
|
||||
IRuntimeCacheProvider cache,
|
||||
DirectoryInfo fileSourceFolder,
|
||||
IEnumerable<LocalizedTextServiceSupplementaryFileSource> supplementFileSources)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (cache == null) throw new ArgumentNullException("cache");
|
||||
if (fileSourceFolder == null) throw new ArgumentNullException("fileSourceFolder");
|
||||
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
|
||||
//Create the lazy source for the _xmlSources
|
||||
_xmlSources = new Lazy<Dictionary<CultureInfo, Lazy<XDocument>>>(() =>
|
||||
{
|
||||
var result = new Dictionary<CultureInfo, Lazy<XDocument>>();
|
||||
|
||||
if (_fileSourceFolder == null) return result;
|
||||
|
||||
foreach (var fileInfo in _fileSourceFolder.GetFiles("*.xml"))
|
||||
{
|
||||
var localCopy = fileInfo;
|
||||
var filename = Path.GetFileNameWithoutExtension(localCopy.FullName).Replace("_", "-");
|
||||
|
||||
//TODO: Fix this nonsense... would have to wait until v8 to store the language files with their correct
|
||||
// names instead of storing them as 2 letters but actually having a 4 letter culture. wtf. So now, we
|
||||
// need to check if the file is 2 letters, then open it to try to find it's 4 letter culture, then use that
|
||||
// if it's successful. We're going to assume (though it seems assuming in the legacy logic is never a great idea)
|
||||
// that any 4 letter file is named with the actual culture that it is!
|
||||
CultureInfo culture = null;
|
||||
if (filename.Length == 2)
|
||||
{
|
||||
//we need to open the file to see if we can read it's 'real' culture, we'll use XmlReader since we don't
|
||||
//want to load in the entire doc into mem just to read a single value
|
||||
using (var fs = fileInfo.OpenRead())
|
||||
using (var reader = XmlReader.Create(fs))
|
||||
{
|
||||
if (reader.IsStartElement())
|
||||
{
|
||||
if (reader.Name == "language")
|
||||
{
|
||||
if (reader.MoveToAttribute("culture"))
|
||||
{
|
||||
var cultureVal = reader.Value;
|
||||
try
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(cultureVal);
|
||||
//add to the tracked dictionary
|
||||
_twoLetterCultureConverter[filename] = culture;
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
Current.Logger.Warn<LocalizedTextServiceFileSources>("The culture {CultureValue} found in the file {CultureFile} is not a valid culture", cultureVal, fileInfo.FullName);
|
||||
//If the culture in the file is invalid, we'll just hope the file name is a valid culture below, otherwise
|
||||
// an exception will be thrown.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (culture == null)
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(filename);
|
||||
}
|
||||
|
||||
//get the lazy value from cache
|
||||
result[culture] = new Lazy<XDocument>(() => _cache.GetCacheItem<XDocument>(
|
||||
string.Format("{0}-{1}", typeof(LocalizedTextServiceFileSources).Name, culture.Name), () =>
|
||||
{
|
||||
XDocument xdoc;
|
||||
|
||||
//load in primary
|
||||
using (var fs = localCopy.OpenRead())
|
||||
{
|
||||
xdoc = XDocument.Load(fs);
|
||||
}
|
||||
|
||||
//load in supplementary
|
||||
MergeSupplementaryFiles(culture, xdoc);
|
||||
|
||||
return xdoc;
|
||||
}, isSliding: true, timeout: TimeSpan.FromMinutes(10), dependentFiles: new[] { localCopy.FullName }));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
if (fileSourceFolder.Exists == false)
|
||||
{
|
||||
Current.Logger.Warn<LocalizedTextServiceFileSources>("The folder does not exist: {FileSourceFolder}, therefore no sources will be discovered", fileSourceFolder.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fileSourceFolder = fileSourceFolder;
|
||||
_supplementFileSources = supplementFileSources;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="fileSourceFolder"></param>
|
||||
public LocalizedTextServiceFileSources(ILogger logger, IRuntimeCacheProvider cache, DirectoryInfo fileSourceFolder)
|
||||
: this(logger, cache, fileSourceFolder, Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns all xml sources for all culture files found in the folder
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IDictionary<CultureInfo, Lazy<XDocument>> GetXmlSources()
|
||||
{
|
||||
return _xmlSources.Value;
|
||||
}
|
||||
|
||||
//TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
|
||||
public Attempt<CultureInfo> TryConvert2LetterCultureTo4Letter(string twoLetterCulture)
|
||||
{
|
||||
if (twoLetterCulture.Length != 2) return Attempt<CultureInfo>.Fail();
|
||||
|
||||
//This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized
|
||||
var resolved = _xmlSources.Value;
|
||||
|
||||
return _twoLetterCultureConverter.ContainsKey(twoLetterCulture)
|
||||
? Attempt.Succeed(_twoLetterCultureConverter[twoLetterCulture])
|
||||
: Attempt<CultureInfo>.Fail();
|
||||
}
|
||||
|
||||
//TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
|
||||
public Attempt<string> TryConvert4LetterCultureTo2Letter(CultureInfo culture)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException("culture");
|
||||
|
||||
//This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized
|
||||
var resolved = _xmlSources.Value;
|
||||
|
||||
return _twoLetterCultureConverter.Values.Contains(culture)
|
||||
? Attempt.Succeed(culture.Name.Substring(0, 2))
|
||||
: Attempt<string>.Fail();
|
||||
}
|
||||
|
||||
private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc)
|
||||
{
|
||||
if (xMasterDoc.Root == null) return;
|
||||
if (_supplementFileSources != null)
|
||||
{
|
||||
//now load in suplementary
|
||||
var found = _supplementFileSources.Where(x =>
|
||||
{
|
||||
var fileName = Path.GetFileName(x.File.FullName);
|
||||
return fileName.InvariantStartsWith(culture.Name) && fileName.InvariantEndsWith(".xml");
|
||||
});
|
||||
|
||||
foreach (var supplementaryFile in found)
|
||||
{
|
||||
using (var fs = supplementaryFile.File.OpenRead())
|
||||
{
|
||||
XDocument xChildDoc;
|
||||
try
|
||||
{
|
||||
xChildDoc = XDocument.Load(fs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error<LocalizedTextServiceFileSources>(ex, "Could not load file into XML {File}", supplementaryFile.File.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (xChildDoc.Root == null) continue;
|
||||
foreach (var xArea in xChildDoc.Root.Elements("area")
|
||||
.Where(x => ((string)x.Attribute("alias")).IsNullOrWhiteSpace() == false))
|
||||
{
|
||||
var areaAlias = (string)xArea.Attribute("alias");
|
||||
|
||||
var areaFound = xMasterDoc.Root.Elements("area").FirstOrDefault(x => ((string)x.Attribute("alias")) == areaAlias);
|
||||
if (areaFound == null)
|
||||
{
|
||||
//add the whole thing
|
||||
xMasterDoc.Root.Add(xArea);
|
||||
}
|
||||
else
|
||||
{
|
||||
MergeChildKeys(xArea, areaFound, supplementaryFile.OverwriteCoreKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MergeChildKeys(XElement source, XElement destination, bool overwrite)
|
||||
{
|
||||
if (destination == null) throw new ArgumentNullException("destination");
|
||||
if (source == null) throw new ArgumentNullException("source");
|
||||
|
||||
//merge in the child elements
|
||||
foreach (var key in source.Elements("key")
|
||||
.Where(x => ((string)x.Attribute("alias")).IsNullOrWhiteSpace() == false))
|
||||
{
|
||||
var keyAlias = (string)key.Attribute("alias");
|
||||
var keyFound = destination.Elements("key").FirstOrDefault(x => ((string)x.Attribute("alias")) == keyAlias);
|
||||
if (keyFound == null)
|
||||
{
|
||||
//append, it doesn't exist
|
||||
destination.Add(key);
|
||||
}
|
||||
else if (overwrite)
|
||||
{
|
||||
//overwrite
|
||||
keyFound.Value = key.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public class LocalizedTextServiceSupplementaryFileSource
|
||||
{
|
||||
|
||||
public LocalizedTextServiceSupplementaryFileSource(FileInfo file, bool overwriteCoreKeys)
|
||||
{
|
||||
if (file == null) throw new ArgumentNullException("file");
|
||||
|
||||
File = file;
|
||||
OverwriteCoreKeys = overwriteCoreKeys;
|
||||
}
|
||||
|
||||
public FileInfo File { get; private set; }
|
||||
public bool OverwriteCoreKeys { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Macro Service, which is an easy access to operations involving <see cref="IMacro"/>
|
||||
/// </summary>
|
||||
internal class MacroService : ScopeRepositoryService, IMacroService
|
||||
{
|
||||
private readonly IMacroRepository _macroRepository;
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
|
||||
public MacroService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IMacroRepository macroRepository, IAuditRepository auditRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_macroRepository = macroRepository;
|
||||
_auditRepository = auditRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMacro"/> object by its alias
|
||||
/// </summary>
|
||||
/// <param name="alias">Alias to retrieve an <see cref="IMacro"/> for</param>
|
||||
/// <returns>An <see cref="IMacro"/> object</returns>
|
||||
public IMacro GetByAlias(string alias)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var q = Query<IMacro>().Where(x => x.Alias == alias);
|
||||
return _macroRepository.Get(q).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IMacro> GetAll()
|
||||
{
|
||||
return GetAll(new int[0]);
|
||||
}
|
||||
|
||||
public IEnumerable<IMacro> GetAll(params int[] ids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _macroRepository.GetMany(ids);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IMacro> GetAll(params Guid[] ids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _macroRepository.GetMany(ids);
|
||||
}
|
||||
}
|
||||
|
||||
public IMacro GetById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _macroRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public IMacro GetById(Guid id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _macroRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an <see cref="IMacro"/>
|
||||
/// </summary>
|
||||
/// <param name="macro"><see cref="IMacro"/> to delete</param>
|
||||
/// <param name="userId">Optional id of the user deleting the macro</param>
|
||||
public void Delete(IMacro macro, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<IMacro>(macro);
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_macroRepository.Delete(macro);
|
||||
deleteEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Deleted, this, deleteEventArgs);
|
||||
Audit(AuditType.Delete, userId, -1);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an <see cref="IMacro"/>
|
||||
/// </summary>
|
||||
/// <param name="macro"><see cref="IMacro"/> to save</param>
|
||||
/// <param name="userId">Optional Id of the user deleting the macro</param>
|
||||
public void Save(IMacro macro, int userId = 0)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IMacro>(macro);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(macro.Name))
|
||||
{
|
||||
throw new ArgumentException("Cannot save macro with empty name.");
|
||||
}
|
||||
|
||||
_macroRepository.Save(macro);
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
Audit(AuditType.Save, userId, -1);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// Gets a list all available <see cref="IMacroPropertyType"/> plugins
|
||||
///// </summary>
|
||||
///// <returns>An enumerable list of <see cref="IMacroPropertyType"/> objects</returns>
|
||||
//public IEnumerable<IMacroPropertyType> GetMacroPropertyTypes()
|
||||
//{
|
||||
// return MacroPropertyTypeResolver.Current.MacroPropertyTypes;
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// Gets an <see cref="IMacroPropertyType"/> by its alias
|
||||
///// </summary>
|
||||
///// <param name="alias">Alias to retrieve an <see cref="IMacroPropertyType"/> for</param>
|
||||
///// <returns>An <see cref="IMacroPropertyType"/> object</returns>
|
||||
//public IMacroPropertyType GetMacroPropertyTypeByAlias(string alias)
|
||||
//{
|
||||
// return MacroPropertyTypeResolver.Current.MacroPropertyTypes.FirstOrDefault(x => x.Alias == alias);
|
||||
//}
|
||||
|
||||
private void Audit(AuditType type, int userId, int objectId)
|
||||
{
|
||||
_auditRepository.Save(new AuditItem(objectId, type, userId, "Macro"));
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
/// <summary>
|
||||
/// Occurs before Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IMacroService, DeleteEventArgs<IMacro>> Deleting;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IMacroService, DeleteEventArgs<IMacro>> Deleted;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IMacroService, SaveEventArgs<IMacro>> Saving;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IMacroService, SaveEventArgs<IMacro>> Saved;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
internal class MediaTypeService : ContentTypeServiceBase<IMediaTypeRepository, IMediaType, IMediaTypeService>, IMediaTypeService
|
||||
{
|
||||
public MediaTypeService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMediaService mediaService,
|
||||
IMediaTypeRepository mediaTypeRepository, IAuditRepository auditRepository, IMediaTypeContainerRepository entityContainerRepository,
|
||||
IEntityRepository entityRepository)
|
||||
: base(provider, logger, eventMessagesFactory, mediaTypeRepository, auditRepository, entityContainerRepository, entityRepository)
|
||||
{
|
||||
MediaService = mediaService;
|
||||
}
|
||||
|
||||
protected override IMediaTypeService This => this;
|
||||
|
||||
// beware! order is important to avoid deadlocks
|
||||
protected override int[] ReadLockIds { get; } = { Constants.Locks.MediaTypes };
|
||||
protected override int[] WriteLockIds { get; } = { Constants.Locks.MediaTree, Constants.Locks.MediaTypes };
|
||||
|
||||
private IMediaService MediaService { get; }
|
||||
|
||||
protected override Guid ContainedObjectType => Constants.ObjectTypes.MediaType;
|
||||
|
||||
protected override void DeleteItemsOfTypes(IEnumerable<int> typeIds)
|
||||
{
|
||||
foreach (var typeId in typeIds)
|
||||
MediaService.DeleteMediaOfType(typeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public class MemberGroupService : RepositoryService, IMemberGroupService
|
||||
{
|
||||
private readonly IMemberGroupRepository _memberGroupRepository;
|
||||
|
||||
public MemberGroupService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IMemberGroupRepository memberGroupRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_memberGroupRepository = memberGroupRepository;
|
||||
//Proxy events!
|
||||
MemberGroupRepository.SavedMemberGroup += MemberGroupRepository_SavedMemberGroup;
|
||||
MemberGroupRepository.SavingMemberGroup += MemberGroupRepository_SavingMemberGroup;
|
||||
}
|
||||
|
||||
#region Proxied event handlers
|
||||
|
||||
void MemberGroupRepository_SavingMemberGroup(IMemberGroupRepository sender, SaveEventArgs<IMemberGroup> e)
|
||||
{
|
||||
// fixme - wtf?
|
||||
// why is the repository triggering these events?
|
||||
// and, the events are *dispatched* by the repository so it makes no sense dispatching them again!
|
||||
|
||||
// v7.6
|
||||
//using (var scope = UowProvider.ScopeProvider.CreateScope())
|
||||
//{
|
||||
// scope.Complete(); // always
|
||||
// if (scope.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMemberGroup>(e.SavedEntities)))
|
||||
// e.Cancel = true;
|
||||
//}
|
||||
|
||||
// v8
|
||||
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMemberGroup>(e.SavedEntities), this))
|
||||
e.Cancel = true;
|
||||
}
|
||||
|
||||
void MemberGroupRepository_SavedMemberGroup(IMemberGroupRepository sender, SaveEventArgs<IMemberGroup> e)
|
||||
{
|
||||
// same as above!
|
||||
|
||||
Saved.RaiseEvent(new SaveEventArgs<IMemberGroup>(e.SavedEntities, false), this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public IEnumerable<IMemberGroup> GetAll()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _memberGroupRepository.GetMany();
|
||||
}
|
||||
}
|
||||
|
||||
public IMemberGroup GetById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _memberGroupRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public IMemberGroup GetByName(string name)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _memberGroupRepository.GetByName(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(IMemberGroup memberGroup, bool raiseEvents = true)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IMemberGroup>(memberGroup);
|
||||
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_memberGroupRepository.Save(memberGroup);
|
||||
scope.Complete();
|
||||
|
||||
if (raiseEvents)
|
||||
{
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(IMemberGroup memberGroup)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<IMemberGroup>(memberGroup);
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_memberGroupRepository.Delete(memberGroup);
|
||||
scope.Complete();
|
||||
deleteEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Deleted, this, deleteEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Delete of a member group
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IMemberGroupService, DeleteEventArgs<IMemberGroup>> Deleting;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Delete of a member group
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IMemberGroupService, DeleteEventArgs<IMemberGroup>> Deleted;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Save of a member group
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We need to proxy these events because the events need to take place at the repo level
|
||||
/// </remarks>
|
||||
public static event TypedEventHandler<IMemberGroupService, SaveEventArgs<IMemberGroup>> Saving;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Save of a member group
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We need to proxy these events because the events need to take place at the repo level
|
||||
/// </remarks>
|
||||
public static event TypedEventHandler<IMemberGroupService, SaveEventArgs<IMemberGroup>> Saved;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
internal class MemberTypeService : ContentTypeServiceBase<IMemberTypeRepository, IMemberType, IMemberTypeService>, IMemberTypeService
|
||||
{
|
||||
private readonly IMemberTypeRepository _memberTypeRepository;
|
||||
|
||||
public MemberTypeService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberService memberService,
|
||||
IMemberTypeRepository memberTypeRepository, IAuditRepository auditRepository, IEntityRepository entityRepository)
|
||||
: base(provider, logger, eventMessagesFactory, memberTypeRepository, auditRepository, null, entityRepository)
|
||||
{
|
||||
MemberService = memberService;
|
||||
_memberTypeRepository = memberTypeRepository;
|
||||
}
|
||||
|
||||
protected override IMemberTypeService This => this;
|
||||
|
||||
// beware! order is important to avoid deadlocks
|
||||
protected override int[] ReadLockIds { get; } = { Constants.Locks.MemberTypes };
|
||||
protected override int[] WriteLockIds { get; } = { Constants.Locks.MemberTree, Constants.Locks.MemberTypes };
|
||||
|
||||
private IMemberService MemberService { get; }
|
||||
|
||||
protected override Guid ContainedObjectType => Constants.ObjectTypes.MemberType;
|
||||
|
||||
protected override void DeleteItemsOfTypes(IEnumerable<int> typeIds)
|
||||
{
|
||||
foreach (var typeId in typeIds)
|
||||
MemberService.DeleteMembersOfType(typeId);
|
||||
}
|
||||
|
||||
public string GetDefault()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
|
||||
using (var e = _memberTypeRepository.GetMany(new int[0]).GetEnumerator())
|
||||
{
|
||||
if (e.MoveNext() == false)
|
||||
throw new InvalidOperationException("No member types could be resolved");
|
||||
var first = e.Current.Alias;
|
||||
var current = true;
|
||||
while (e.Current.Alias.InvariantEquals("Member") == false && (current = e.MoveNext()))
|
||||
{ }
|
||||
return current ? e.Current.Alias : first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,555 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public class NotificationService : INotificationService
|
||||
{
|
||||
private readonly IScopeProvider _uowProvider;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly INotificationsRepository _notificationsRepository;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IContentSection _contentSection;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public NotificationService(IScopeProvider provider, IUserService userService, IContentService contentService, ILocalizationService localizationService,
|
||||
ILogger logger, INotificationsRepository notificationsRepository, IGlobalSettings globalSettings, IContentSection contentSection)
|
||||
{
|
||||
_notificationsRepository = notificationsRepository;
|
||||
_globalSettings = globalSettings;
|
||||
_contentSection = contentSection;
|
||||
_uowProvider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
|
||||
_localizationService = localizationService;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the previous version to the latest version of the content item if there is one
|
||||
/// </summary>
|
||||
/// <param name="contentId"></param>
|
||||
/// <returns></returns>
|
||||
private IContentBase GetPreviousVersion(int contentId)
|
||||
{
|
||||
// Regarding this: http://issues.umbraco.org/issue/U4-5180
|
||||
// we know they are descending from the service so we know that newest is first
|
||||
// we are only selecting the top 2 rows since that is all we need
|
||||
var allVersions = _contentService.GetVersionIds(contentId, 2).ToList();
|
||||
var prevVersionIndex = allVersions.Count > 1 ? 1 : 0;
|
||||
return _contentService.GetVersion(allVersions[prevVersionIndex]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the notifications for the specified user regarding the specified node and action.
|
||||
/// </summary>
|
||||
/// <param name="entities"></param>
|
||||
/// <param name="operatingUser"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="actionName"></param>
|
||||
/// <param name="siteUri"></param>
|
||||
/// <param name="createSubject"></param>
|
||||
/// <param name="createBody"></param>
|
||||
public void SendNotifications(IUser operatingUser, IEnumerable<IContent> entities, string action, string actionName, Uri siteUri,
|
||||
Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
|
||||
Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody)
|
||||
{
|
||||
var entitiesL = entities.ToList();
|
||||
|
||||
//exit if there are no entities
|
||||
if (entitiesL.Count == 0) return;
|
||||
|
||||
//put all entity's paths into a list with the same indicies
|
||||
var paths = entitiesL.Select(x => x.Path.Split(',').Select(int.Parse).ToArray()).ToArray();
|
||||
|
||||
// lazily get versions
|
||||
var prevVersionDictionary = new Dictionary<int, IContentBase>();
|
||||
|
||||
// see notes above
|
||||
var id = Constants.Security.SuperUserId;
|
||||
const int pagesz = 400; // load batches of 400 users
|
||||
do
|
||||
{
|
||||
// users are returned ordered by id, notifications are returned ordered by user id
|
||||
var users = ((UserService)_userService).GetNextUsers(id, pagesz).Where(x => x.IsApproved).ToList();
|
||||
var notifications = GetUsersNotifications(users.Select(x => x.Id), action, Enumerable.Empty<int>(), Constants.ObjectTypes.Document).ToList();
|
||||
if (notifications.Count == 0) break;
|
||||
|
||||
var i = 0;
|
||||
foreach (var user in users)
|
||||
{
|
||||
// continue if there's no notification for this user
|
||||
if (notifications[i].UserId != user.Id) continue; // next user
|
||||
|
||||
for (var j = 0; j < entitiesL.Count; j++)
|
||||
{
|
||||
var content = entitiesL[j];
|
||||
var path = paths[j];
|
||||
|
||||
// test if the notification applies to the path ie to this entity
|
||||
if (path.Contains(notifications[i].EntityId) == false) continue; // next entity
|
||||
|
||||
if (prevVersionDictionary.ContainsKey(content.Id) == false)
|
||||
{
|
||||
prevVersionDictionary[content.Id] = GetPreviousVersion(content.Id);
|
||||
}
|
||||
|
||||
// queue notification
|
||||
var req = CreateNotificationRequest(operatingUser, user, content, prevVersionDictionary[content.Id], actionName, siteUri, createSubject, createBody);
|
||||
Enqueue(req);
|
||||
}
|
||||
|
||||
// skip other notifications for this user, essentially this means moving i to the next index of notifications
|
||||
// for the next user.
|
||||
do
|
||||
{
|
||||
i++;
|
||||
} while (i < notifications.Count && notifications[i].UserId == user.Id);
|
||||
|
||||
if (i >= notifications.Count) break; // break if no more notifications
|
||||
}
|
||||
|
||||
// load more users if any
|
||||
id = users.Count == pagesz ? users.Last().Id + 1 : -1;
|
||||
|
||||
} while (id > 0);
|
||||
}
|
||||
|
||||
private IEnumerable<Notification> GetUsersNotifications(IEnumerable<int> userIds, string action, IEnumerable<int> nodeIds, Guid objectType)
|
||||
{
|
||||
using (var scope = _uowProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _notificationsRepository.GetUsersNotifications(userIds, action, nodeIds, objectType);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the notifications for the user
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Notification> GetUserNotifications(IUser user)
|
||||
{
|
||||
using (var scope = _uowProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _notificationsRepository.GetUserNotifications(user);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the notifications for the user based on the specified node path
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors)
|
||||
/// </remarks>
|
||||
public IEnumerable<Notification> GetUserNotifications(IUser user, string path)
|
||||
{
|
||||
var userNotifications = GetUserNotifications(user);
|
||||
return FilterUserNotificationsByPath(userNotifications, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters a userNotifications collection by a path
|
||||
/// </summary>
|
||||
/// <param name="userNotifications"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Notification> FilterUserNotificationsByPath(IEnumerable<Notification> userNotifications, string path)
|
||||
{
|
||||
var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
|
||||
return userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes notifications by entity
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
public IEnumerable<Notification> GetEntityNotifications(IEntity entity)
|
||||
{
|
||||
using (var scope = _uowProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _notificationsRepository.GetEntityNotifications(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes notifications by entity
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
public void DeleteNotifications(IEntity entity)
|
||||
{
|
||||
using (var scope = _uowProvider.CreateScope())
|
||||
{
|
||||
_notificationsRepository.DeleteNotifications(entity);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes notifications by user
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
public void DeleteNotifications(IUser user)
|
||||
{
|
||||
using (var scope = _uowProvider.CreateScope())
|
||||
{
|
||||
_notificationsRepository.DeleteNotifications(user);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete notifications by user and entity
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="entity"></param>
|
||||
public void DeleteNotifications(IUser user, IEntity entity)
|
||||
{
|
||||
using (var scope = _uowProvider.CreateScope())
|
||||
{
|
||||
_notificationsRepository.DeleteNotifications(user, entity);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specific notifications for the user and entity
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="actions"></param>
|
||||
/// <remarks>
|
||||
/// This performs a full replace
|
||||
/// </remarks>
|
||||
public IEnumerable<Notification> SetNotifications(IUser user, IEntity entity, string[] actions)
|
||||
{
|
||||
using (var scope = _uowProvider.CreateScope())
|
||||
{
|
||||
var notifications = _notificationsRepository.SetNotifications(user, entity, actions);
|
||||
scope.Complete();
|
||||
return notifications;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new notification
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="action">The action letter - note: this is a string for future compatibility</param>
|
||||
/// <returns></returns>
|
||||
public Notification CreateNotification(IUser user, IEntity entity, string action)
|
||||
{
|
||||
using (var scope = _uowProvider.CreateScope())
|
||||
{
|
||||
var notification = _notificationsRepository.CreateNotification(user, entity, action);
|
||||
scope.Complete();
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
|
||||
#region private methods
|
||||
|
||||
/// <summary>
|
||||
/// Sends the notification
|
||||
/// </summary>
|
||||
/// <param name="performingUser"></param>
|
||||
/// <param name="mailingUser"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="oldDoc"></param>
|
||||
/// <param name="actionName">The action readable name - currently an action is just a single letter, this is the name associated with the letter </param>
|
||||
/// <param name="siteUri"></param>
|
||||
/// <param name="createSubject">Callback to create the mail subject</param>
|
||||
/// <param name="createBody">Callback to create the mail body</param>
|
||||
private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContent content, IContentBase oldDoc,
|
||||
string actionName,
|
||||
Uri siteUri,
|
||||
Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
|
||||
Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody)
|
||||
{
|
||||
if (performingUser == null) throw new ArgumentNullException("performingUser");
|
||||
if (mailingUser == null) throw new ArgumentNullException("mailingUser");
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (siteUri == null) throw new ArgumentNullException("siteUri");
|
||||
if (createSubject == null) throw new ArgumentNullException("createSubject");
|
||||
if (createBody == null) throw new ArgumentNullException("createBody");
|
||||
|
||||
// build summary
|
||||
var summary = new StringBuilder();
|
||||
|
||||
if (content.ContentType.VariesByNothing())
|
||||
{
|
||||
if (!_contentSection.DisableHtmlEmail)
|
||||
{
|
||||
//create the html summary for invariant content
|
||||
|
||||
//list all of the property values like we used to
|
||||
summary.Append("<table style=\"width: 100 %; \">");
|
||||
foreach (var p in content.Properties)
|
||||
{
|
||||
//fixme doesn't take into account variants
|
||||
|
||||
var newText = p.GetValue() != null ? p.GetValue().ToString() : "";
|
||||
var oldText = newText;
|
||||
|
||||
// check if something was changed and display the changes otherwise display the fields
|
||||
if (oldDoc.Properties.Contains(p.PropertyType.Alias))
|
||||
{
|
||||
var oldProperty = oldDoc.Properties[p.PropertyType.Alias];
|
||||
oldText = oldProperty.GetValue() != null ? oldProperty.GetValue().ToString() : "";
|
||||
|
||||
// replace html with char equivalent
|
||||
ReplaceHtmlSymbols(ref oldText);
|
||||
ReplaceHtmlSymbols(ref newText);
|
||||
}
|
||||
|
||||
//show the values
|
||||
summary.Append("<tr>");
|
||||
summary.Append("<th style='text-align: left; vertical-align: top; width: 25%;border-bottom: 1px solid #CCC'>");
|
||||
summary.Append(p.PropertyType.Name);
|
||||
summary.Append("</th>");
|
||||
summary.Append("<td style='text-align: left; vertical-align: top;border-bottom: 1px solid #CCC'>");
|
||||
summary.Append(newText);
|
||||
summary.Append("</td>");
|
||||
summary.Append("</tr>");
|
||||
}
|
||||
summary.Append("</table>");
|
||||
}
|
||||
|
||||
}
|
||||
else if (content.ContentType.VariesByCulture())
|
||||
{
|
||||
//it's variant, so detect what cultures have changed
|
||||
|
||||
if (!_contentSection.DisableHtmlEmail)
|
||||
{
|
||||
//Create the html based summary (ul of culture names)
|
||||
|
||||
var culturesChanged = content.CultureInfos.Where(x => x.Value.WasDirty())
|
||||
.Select(x => x.Key)
|
||||
.Select(_localizationService.GetLanguageByIsoCode)
|
||||
.WhereNotNull()
|
||||
.Select(x => x.CultureName);
|
||||
summary.Append("<ul>");
|
||||
foreach (var culture in culturesChanged)
|
||||
{
|
||||
summary.Append("<li>");
|
||||
summary.Append(culture);
|
||||
summary.Append("</li>");
|
||||
}
|
||||
summary.Append("</ul>");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Create the text based summary (csv of culture names)
|
||||
|
||||
var culturesChanged = string.Join(", ", content.CultureInfos.Where(x => x.Value.WasDirty())
|
||||
.Select(x => x.Key)
|
||||
.Select(_localizationService.GetLanguageByIsoCode)
|
||||
.WhereNotNull()
|
||||
.Select(x => x.CultureName));
|
||||
|
||||
summary.Append("'");
|
||||
summary.Append(culturesChanged);
|
||||
summary.Append("'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//not supported yet...
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
var protocol = _globalSettings.UseHttps ? "https" : "http";
|
||||
|
||||
var subjectVars = new NotificationEmailSubjectParams(
|
||||
string.Concat(siteUri.Authority, IOHelper.ResolveUrl(SystemDirectories.Umbraco)),
|
||||
actionName,
|
||||
content.Name);
|
||||
|
||||
var bodyVars = new NotificationEmailBodyParams(
|
||||
mailingUser.Name,
|
||||
actionName,
|
||||
content.Name,
|
||||
content.Id.ToString(CultureInfo.InvariantCulture),
|
||||
string.Format("{2}://{0}/{1}",
|
||||
string.Concat(siteUri.Authority),
|
||||
//TODO: RE-enable this so we can have a nice url
|
||||
/*umbraco.library.NiceUrl(documentObject.Id))*/
|
||||
string.Concat(content.Id, ".aspx"),
|
||||
protocol),
|
||||
performingUser.Name,
|
||||
string.Concat(siteUri.Authority, IOHelper.ResolveUrl(SystemDirectories.Umbraco)),
|
||||
summary.ToString());
|
||||
|
||||
// create the mail message
|
||||
var mail = new MailMessage(_contentSection.NotificationEmailAddress, mailingUser.Email);
|
||||
|
||||
// populate the message
|
||||
|
||||
|
||||
mail.Subject = createSubject((mailingUser, subjectVars));
|
||||
if (_contentSection.DisableHtmlEmail)
|
||||
{
|
||||
mail.IsBodyHtml = false;
|
||||
mail.Body = createBody((user: mailingUser, body: bodyVars, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
mail.IsBodyHtml = true;
|
||||
mail.Body =
|
||||
string.Concat(@"<html><head>
|
||||
</head>
|
||||
<body style='font-family: Trebuchet MS, arial, sans-serif; font-color: black;'>
|
||||
", createBody((user: mailingUser, body: bodyVars, true)));
|
||||
}
|
||||
|
||||
// nh, issue 30724. Due to hardcoded http strings in resource files, we need to check for https replacements here
|
||||
// adding the server name to make sure we don't replace external links
|
||||
if (_globalSettings.UseHttps && string.IsNullOrEmpty(mail.Body) == false)
|
||||
{
|
||||
string serverName = siteUri.Host;
|
||||
mail.Body = mail.Body.Replace(
|
||||
string.Format("http://{0}", serverName),
|
||||
string.Format("https://{0}", serverName));
|
||||
}
|
||||
|
||||
return new NotificationRequest(mail, actionName, mailingUser.Name, mailingUser.Email);
|
||||
}
|
||||
|
||||
private string ReplaceLinks(string text, Uri siteUri)
|
||||
{
|
||||
var sb = new StringBuilder(_globalSettings.UseHttps ? "https://" : "http://");
|
||||
sb.Append(siteUri.Authority);
|
||||
sb.Append("/");
|
||||
var domain = sb.ToString();
|
||||
text = text.Replace("href=\"/", "href=\"" + domain);
|
||||
text = text.Replace("src=\"/", "src=\"" + domain);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the HTML symbols with the character equivalent.
|
||||
/// </summary>
|
||||
/// <param name="oldString">The old string.</param>
|
||||
private static void ReplaceHtmlSymbols(ref string oldString)
|
||||
{
|
||||
if (oldString.IsNullOrWhiteSpace()) return;
|
||||
oldString = oldString.Replace(" ", " ");
|
||||
oldString = oldString.Replace("’", "'");
|
||||
oldString = oldString.Replace("&", "&");
|
||||
oldString = oldString.Replace("“", "“");
|
||||
oldString = oldString.Replace("”", "”");
|
||||
oldString = oldString.Replace(""", "\"");
|
||||
}
|
||||
|
||||
// manage notifications
|
||||
// ideally, would need to use IBackgroundTasks - but they are not part of Core!
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
private static readonly BlockingCollection<NotificationRequest> Queue = new BlockingCollection<NotificationRequest>();
|
||||
private static volatile bool _running;
|
||||
|
||||
private void Enqueue(NotificationRequest notification)
|
||||
{
|
||||
Queue.Add(notification);
|
||||
if (_running) return;
|
||||
lock (Locker)
|
||||
{
|
||||
if (_running) return;
|
||||
Process(Queue);
|
||||
_running = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class NotificationRequest
|
||||
{
|
||||
public NotificationRequest(MailMessage mail, string action, string userName, string email)
|
||||
{
|
||||
Mail = mail;
|
||||
Action = action;
|
||||
UserName = userName;
|
||||
Email = email;
|
||||
}
|
||||
|
||||
public MailMessage Mail { get; private set; }
|
||||
|
||||
public string Action { get; private set; }
|
||||
|
||||
public string UserName { get; private set; }
|
||||
|
||||
public string Email { get; private set; }
|
||||
}
|
||||
|
||||
private void Process(BlockingCollection<NotificationRequest> notificationRequests)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(state =>
|
||||
{
|
||||
var s = new SmtpClient();
|
||||
try
|
||||
{
|
||||
_logger.Debug<NotificationService>("Begin processing notifications.");
|
||||
while (true)
|
||||
{
|
||||
NotificationRequest request;
|
||||
while (notificationRequests.TryTake(out request, 8 * 1000)) // stay on for 8s
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Sendmail != null) Sendmail(s, request.Mail, _logger); else s.Send(request.Mail);
|
||||
_logger.Debug<NotificationService>("Notification '{Action}' sent to {Username} ({Email})", request.Action, request.UserName, request.Email);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error<NotificationService>(ex, "An error occurred sending notification");
|
||||
s.Dispose();
|
||||
s = new SmtpClient();
|
||||
}
|
||||
finally
|
||||
{
|
||||
request.Mail.Dispose();
|
||||
}
|
||||
}
|
||||
lock (Locker)
|
||||
{
|
||||
if (notificationRequests.Count > 0) continue; // last chance
|
||||
_running = false; // going down
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
_logger.Debug<NotificationService>("Done processing notifications.");
|
||||
});
|
||||
}
|
||||
|
||||
// for tests
|
||||
internal static Action<SmtpClient, MailMessage, ILogger> Sendmail;
|
||||
//= (_, msg, logger) => logger.Debug<NotificationService>("Email " + msg.To.ToString());
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,263 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public class PublicAccessService : ScopeRepositoryService, IPublicAccessService
|
||||
{
|
||||
private readonly IPublicAccessRepository _publicAccessRepository;
|
||||
|
||||
public PublicAccessService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IPublicAccessRepository publicAccessRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_publicAccessRepository = publicAccessRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all defined entries and associated rules
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<PublicAccessEntry> GetAll()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _publicAccessRepository.GetMany();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entry defined for the content item's path
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns>Returns null if no entry is found</returns>
|
||||
public PublicAccessEntry GetEntryForContent(IContent content)
|
||||
{
|
||||
return GetEntryForContent(content.Path.EnsureEndsWith("," + content.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entry defined for the content item based on a content path
|
||||
/// </summary>
|
||||
/// <param name="contentPath"></param>
|
||||
/// <returns>Returns null if no entry is found</returns>
|
||||
/// <remarks>
|
||||
/// NOTE: This method get's called *very* often! This will return the results from cache
|
||||
/// </remarks>
|
||||
public PublicAccessEntry GetEntryForContent(string contentPath)
|
||||
{
|
||||
//Get all ids in the path for the content item and ensure they all
|
||||
// parse to ints that are not -1.
|
||||
var ids = contentPath.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => int.TryParse(x, out int val) ? val : -1)
|
||||
.Where(x => x != -1)
|
||||
.ToList();
|
||||
|
||||
//start with the deepest id
|
||||
ids.Reverse();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
//This will retrieve from cache!
|
||||
var entries = _publicAccessRepository.GetMany().ToArray();
|
||||
|
||||
scope.Complete();
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var found = entries.FirstOrDefault(x => x.ProtectedNodeId == id);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the content has an entry for it's path
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<PublicAccessEntry> IsProtected(IContent content)
|
||||
{
|
||||
var result = GetEntryForContent(content);
|
||||
return Attempt.If(result != null, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the content has an entry based on a content path
|
||||
/// </summary>
|
||||
/// <param name="contentPath"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<PublicAccessEntry> IsProtected(string contentPath)
|
||||
{
|
||||
var result = GetEntryForContent(contentPath);
|
||||
return Attempt.If(result != null, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a rule
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="ruleType"></param>
|
||||
/// <param name="ruleValue"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<OperationResult<OperationResultType, PublicAccessEntry>> AddRule(IContent content, string ruleType, string ruleValue)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
PublicAccessEntry entry;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id);
|
||||
if (entry == null)
|
||||
return OperationResult.Attempt.Cannot<PublicAccessEntry>(evtMsgs); // causes rollback // causes rollback
|
||||
|
||||
var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue);
|
||||
if (existingRule == null)
|
||||
{
|
||||
entry.AddRule(ruleValue, ruleType);
|
||||
}
|
||||
else
|
||||
{
|
||||
//If they are both the same already then there's nothing to update, exit
|
||||
//If they are both the same already then there's nothing to update, exit
|
||||
return OperationResult.Attempt.Succeed(evtMsgs, entry);
|
||||
}
|
||||
|
||||
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs, entry);
|
||||
}
|
||||
|
||||
_publicAccessRepository.Save(entry);
|
||||
|
||||
scope.Complete();
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs, entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a rule
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="ruleType"></param>
|
||||
/// <param name="ruleValue"></param>
|
||||
public Attempt<OperationResult> RemoveRule(IContent content, string ruleType, string ruleValue)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
PublicAccessEntry entry;
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id);
|
||||
if (entry == null) return Attempt<OperationResult>.Fail(); // causes rollback // causes rollback
|
||||
|
||||
var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue);
|
||||
if (existingRule == null) return Attempt<OperationResult>.Fail(); // causes rollback // causes rollback
|
||||
|
||||
entry.RemoveRule(existingRule);
|
||||
|
||||
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
_publicAccessRepository.Save(entry);
|
||||
scope.Complete();
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the entry
|
||||
/// </summary>
|
||||
/// <param name="entry"></param>
|
||||
public Attempt<OperationResult> Save(PublicAccessEntry entry)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
_publicAccessRepository.Save(entry);
|
||||
scope.Complete();
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the entry and all associated rules
|
||||
/// </summary>
|
||||
/// <param name="entry"></param>
|
||||
public Attempt<OperationResult> Delete(PublicAccessEntry entry)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<PublicAccessEntry>(entry, evtMsgs);
|
||||
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
_publicAccessRepository.Delete(entry);
|
||||
scope.Complete();
|
||||
|
||||
deleteEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Deleted, this, deleteEventArgs);
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IPublicAccessService, SaveEventArgs<PublicAccessEntry>> Saving;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Save
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IPublicAccessService, SaveEventArgs<PublicAccessEntry>> Saved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>> Deleting;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after Delete
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>> Deleted;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
internal class RedirectUrlService : ScopeRepositoryService, IRedirectUrlService
|
||||
{
|
||||
private readonly IRedirectUrlRepository _redirectUrlRepository;
|
||||
|
||||
public RedirectUrlService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IRedirectUrlRepository redirectUrlRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_redirectUrlRepository = redirectUrlRepository;
|
||||
}
|
||||
|
||||
public void Register(string url, Guid contentKey)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var redir = _redirectUrlRepository.Get(url, contentKey);
|
||||
if (redir != null)
|
||||
redir.CreateDateUtc = DateTime.UtcNow;
|
||||
else
|
||||
redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey };
|
||||
_redirectUrlRepository.Save(redir);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(IRedirectUrl redirectUrl)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_redirectUrlRepository.Delete(redirectUrl);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(Guid id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_redirectUrlRepository.Delete(id);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteContentRedirectUrls(Guid contentKey)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_redirectUrlRepository.DeleteContentUrls(contentKey);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteAll()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
_redirectUrlRepository.DeleteAll();
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public IRedirectUrl GetMostRecentRedirectUrl(string url)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _redirectUrlRepository.GetMostRecentUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRedirectUrl> GetContentRedirectUrls(Guid contentKey)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _redirectUrlRepository.GetContentUrls(contentKey);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRedirectUrl> GetAllRedirectUrls(long pageIndex, int pageSize, out long total)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _redirectUrlRepository.GetAllUrls(pageIndex, pageSize, out total);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRedirectUrl> GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _redirectUrlRepository.GetAllUrls(rootContentId, pageIndex, pageSize, out total);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRedirectUrl> SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _redirectUrlRepository.SearchUrls(searchTerm, pageIndex, pageSize, out total);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,720 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
public class RelationService : ScopeRepositoryService, IRelationService
|
||||
{
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IRelationRepository _relationRepository;
|
||||
private readonly IRelationTypeRepository _relationTypeRepository;
|
||||
|
||||
public RelationService(IScopeProvider uowProvider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IEntityService entityService,
|
||||
IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository)
|
||||
: base(uowProvider, logger, eventMessagesFactory)
|
||||
{
|
||||
_relationRepository = relationRepository;
|
||||
_relationTypeRepository = relationTypeRepository;
|
||||
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Relation"/> by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="Relation"/></param>
|
||||
/// <returns>A <see cref="Relation"/> object</returns>
|
||||
public IRelation GetById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _relationRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="RelationType"/> by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="RelationType"/></param>
|
||||
/// <returns>A <see cref="RelationType"/> object</returns>
|
||||
public IRelationType GetRelationTypeById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _relationTypeRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="RelationType"/> by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the <see cref="RelationType"/></param>
|
||||
/// <returns>A <see cref="RelationType"/> object</returns>
|
||||
public IRelationType GetRelationTypeById(Guid id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _relationTypeRepository.Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="RelationType"/> by its Alias
|
||||
/// </summary>
|
||||
/// <param name="alias">Alias of the <see cref="RelationType"/></param>
|
||||
/// <returns>A <see cref="RelationType"/> object</returns>
|
||||
public IRelationType GetRelationTypeByAlias(string alias)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelationType>().Where(x => x.Alias == alias);
|
||||
return _relationTypeRepository.Get(query).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="Relation"/> objects
|
||||
/// </summary>
|
||||
/// <param name="ids">Optional array of integer ids to return relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetAllRelations(params int[] ids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _relationRepository.GetMany(ids);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
/// <param name="relationType"><see cref="RelationType"/> to retrieve Relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetAllRelationsByRelationType(RelationType relationType)
|
||||
{
|
||||
return GetAllRelationsByRelationType(relationType.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>'s Id
|
||||
/// </summary>
|
||||
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetAllRelationsByRelationType(int relationTypeId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.RelationTypeId == relationTypeId);
|
||||
return _relationRepository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="Relation"/> objects
|
||||
/// </summary>
|
||||
/// <param name="ids">Optional array of integer ids to return relationtypes for</param>
|
||||
/// <returns>An enumerable list of <see cref="RelationType"/> objects</returns>
|
||||
public IEnumerable<IRelationType> GetAllRelationTypes(params int[] ids)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _relationTypeRepository.GetMany(ids);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their parent Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the parent to retrieve relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByParentId(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.ParentId == id);
|
||||
return _relationRepository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their parent entity
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent Entity to retrieve relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent)
|
||||
{
|
||||
return GetByParentId(parent.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their parent entity
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent Entity to retrieve relations for</param>
|
||||
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent, string relationTypeAlias)
|
||||
{
|
||||
return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their child Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the child to retrieve relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByChildId(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.ChildId == id);
|
||||
return _relationRepository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their child Entity
|
||||
/// </summary>
|
||||
/// <param name="child">Child Entity to retrieve relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child)
|
||||
{
|
||||
return GetByChildId(child.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their child Entity
|
||||
/// </summary>
|
||||
/// <param name="child">Child Entity to retrieve relations for</param>
|
||||
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child, string relationTypeAlias)
|
||||
{
|
||||
return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by their child or parent Id.
|
||||
/// Using this method will get you all relations regards of it being a child or parent relation.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the child or parent to retrieve relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByParentOrChildId(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.ChildId == id || x.ParentId == id);
|
||||
return _relationRepository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRelation> GetByParentOrChildId(int id, string relationTypeAlias)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var rtQuery = Query<IRelationType>().Where(x => x.Alias == relationTypeAlias);
|
||||
var relationType = _relationTypeRepository.Get(rtQuery).FirstOrDefault();
|
||||
if (relationType == null)
|
||||
return Enumerable.Empty<IRelation>();
|
||||
|
||||
var query = Query<IRelation>().Where(x => (x.ChildId == id || x.ParentId == id) && x.RelationTypeId == relationType.Id);
|
||||
return _relationRepository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by the Name of the <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
/// <param name="relationTypeName">Name of the <see cref="RelationType"/> to retrieve Relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByRelationTypeName(string relationTypeName)
|
||||
{
|
||||
List<int> relationTypeIds;
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelationType>().Where(x => x.Name == relationTypeName);
|
||||
var relationTypes = _relationTypeRepository.Get(query);
|
||||
relationTypeIds = relationTypes.Select(x => x.Id).ToList();
|
||||
}
|
||||
|
||||
return relationTypeIds.Count == 0
|
||||
? Enumerable.Empty<IRelation>()
|
||||
: GetRelationsByListOfTypeIds(relationTypeIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by the Alias of the <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
/// <param name="relationTypeAlias">Alias of the <see cref="RelationType"/> to retrieve Relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByRelationTypeAlias(string relationTypeAlias)
|
||||
{
|
||||
List<int> relationTypeIds;
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelationType>().Where(x => x.Alias == relationTypeAlias);
|
||||
var relationTypes = _relationTypeRepository.Get(query);
|
||||
relationTypeIds = relationTypes.Select(x => x.Id).ToList();
|
||||
}
|
||||
|
||||
return relationTypeIds.Count == 0
|
||||
? Enumerable.Empty<IRelation>()
|
||||
: GetRelationsByListOfTypeIds(relationTypeIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="Relation"/> objects by the Id of the <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
|
||||
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
|
||||
public IEnumerable<IRelation> GetByRelationTypeId(int relationTypeId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.RelationTypeId == relationTypeId);
|
||||
return _relationRepository.Get(query);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Child object from a Relation as an <see cref="IUmbracoEntity"/>
|
||||
/// </summary>
|
||||
/// <param name="relation">Relation to retrieve child object from</param>
|
||||
/// <param name="loadBaseType">Optional bool to load the complete object graph when set to <c>False</c></param>
|
||||
/// <returns>An <see cref="IUmbracoEntity"/></returns>
|
||||
public IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false)
|
||||
{
|
||||
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
|
||||
return _entityService.Get(relation.ChildId, objectType, loadBaseType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Parent object from a Relation as an <see cref="IUmbracoEntity"/>
|
||||
/// </summary>
|
||||
/// <param name="relation">Relation to retrieve parent object from</param>
|
||||
/// <param name="loadBaseType">Optional bool to load the complete object graph when set to <c>False</c></param>
|
||||
/// <returns>An <see cref="IUmbracoEntity"/></returns>
|
||||
public IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false)
|
||||
{
|
||||
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
|
||||
return _entityService.Get(relation.ParentId, objectType, loadBaseType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Parent and Child objects from a Relation as a <see cref="Tuple"/>"/> with <see cref="IUmbracoEntity"/>.
|
||||
/// </summary>
|
||||
/// <param name="relation">Relation to retrieve parent and child object from</param>
|
||||
/// <param name="loadBaseType">Optional bool to load the complete object graph when set to <c>False</c></param>
|
||||
/// <returns>Returns a Tuple with Parent (item1) and Child (item2)</returns>
|
||||
public Tuple<IUmbracoEntity, IUmbracoEntity> GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false)
|
||||
{
|
||||
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
|
||||
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
|
||||
|
||||
var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType);
|
||||
var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType);
|
||||
|
||||
return new Tuple<IUmbracoEntity, IUmbracoEntity>(parent, child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Child objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
|
||||
/// </summary>
|
||||
/// <param name="relations">List of relations to retrieve child objects from</param>
|
||||
/// <param name="loadBaseType">Optional bool to load the complete object graph when set to <c>False</c></param>
|
||||
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/></returns>
|
||||
public IEnumerable<IUmbracoEntity> GetChildEntitiesFromRelations(IEnumerable<IRelation> relations, bool loadBaseType = false)
|
||||
{
|
||||
foreach (var relation in relations)
|
||||
{
|
||||
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
|
||||
yield return _entityService.Get(relation.ChildId, objectType, loadBaseType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Parent objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
|
||||
/// </summary>
|
||||
/// <param name="relations">List of relations to retrieve parent objects from</param>
|
||||
/// <param name="loadBaseType">Optional bool to load the complete object graph when set to <c>False</c></param>
|
||||
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/></returns>
|
||||
public IEnumerable<IUmbracoEntity> GetParentEntitiesFromRelations(IEnumerable<IRelation> relations, bool loadBaseType = false)
|
||||
{
|
||||
foreach (var relation in relations)
|
||||
{
|
||||
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
|
||||
yield return _entityService.Get(relation.ParentId, objectType, loadBaseType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Parent and Child objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
|
||||
/// </summary>
|
||||
/// <param name="relations">List of relations to retrieve parent and child objects from</param>
|
||||
/// <param name="loadBaseType">Optional bool to load the complete object graph when set to <c>False</c></param>
|
||||
/// <returns>An enumerable list of <see cref="Tuple"/> with <see cref="IUmbracoEntity"/></returns>
|
||||
public IEnumerable<Tuple<IUmbracoEntity, IUmbracoEntity>> GetEntitiesFromRelations(IEnumerable<IRelation> relations, bool loadBaseType = false)
|
||||
{
|
||||
foreach (var relation in relations)
|
||||
{
|
||||
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
|
||||
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
|
||||
|
||||
var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType);
|
||||
var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType);
|
||||
|
||||
yield return new Tuple<IUmbracoEntity, IUmbracoEntity>(parent, child);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relates two objects by their entity Ids.
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the parent</param>
|
||||
/// <param name="childId">Id of the child</param>
|
||||
/// <param name="relationType">The type of relation to create</param>
|
||||
/// <returns>The created <see cref="Relation"/></returns>
|
||||
public IRelation Relate(int parentId, int childId, IRelationType relationType)
|
||||
{
|
||||
// Ensure that the RelationType has an indentity before using it to relate two entities
|
||||
if (relationType.HasIdentity == false)
|
||||
Save(relationType);
|
||||
|
||||
var relation = new Relation(parentId, childId, relationType);
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IRelation>(relation);
|
||||
if (scope.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return relation; // fixme - returning sth that does not exist here?! // fixme - returning sth that does not exist here?!
|
||||
}
|
||||
|
||||
_relationRepository.Save(relation);
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(SavedRelation, this, saveEventArgs);
|
||||
scope.Complete();
|
||||
return relation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relates two objects that are based on the <see cref="IUmbracoEntity"/> interface.
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent entity</param>
|
||||
/// <param name="child">Child entity</param>
|
||||
/// <param name="relationType">The type of relation to create</param>
|
||||
/// <returns>The created <see cref="Relation"/></returns>
|
||||
public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType)
|
||||
{
|
||||
return Relate(parent.Id, child.Id, relationType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relates two objects by their entity Ids.
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the parent</param>
|
||||
/// <param name="childId">Id of the child</param>
|
||||
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
|
||||
/// <returns>The created <see cref="Relation"/></returns>
|
||||
public IRelation Relate(int parentId, int childId, string relationTypeAlias)
|
||||
{
|
||||
var relationType = GetRelationTypeByAlias(relationTypeAlias);
|
||||
if (relationType == null || string.IsNullOrEmpty(relationType.Alias))
|
||||
throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias));
|
||||
|
||||
return Relate(parentId, childId, relationType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relates two objects that are based on the <see cref="IUmbracoEntity"/> interface.
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent entity</param>
|
||||
/// <param name="child">Child entity</param>
|
||||
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
|
||||
/// <returns>The created <see cref="Relation"/></returns>
|
||||
public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias)
|
||||
{
|
||||
var relationType = GetRelationTypeByAlias(relationTypeAlias);
|
||||
if (relationType == null || string.IsNullOrEmpty(relationType.Alias))
|
||||
throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias));
|
||||
|
||||
return Relate(parent.Id, child.Id, relationType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether any relations exists for the passed in <see cref="RelationType"/>.
|
||||
/// </summary>
|
||||
/// <param name="relationType"><see cref="RelationType"/> to check for relations</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exists for the given <see cref="RelationType"/>, otherwise <c>False</c></returns>
|
||||
public bool HasRelations(IRelationType relationType)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.RelationTypeId == relationType.Id);
|
||||
return _relationRepository.Get(query).Any();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether any relations exists for the passed in Id.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of an object to check relations for</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exists with the given Id, otherwise <c>False</c></returns>
|
||||
public bool IsRelated(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.ParentId == id || x.ChildId == id);
|
||||
return _relationRepository.Get(query).Any();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two items are related
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the Parent relation</param>
|
||||
/// <param name="childId">Id of the Child relation</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exists with the given Ids, otherwise <c>False</c></returns>
|
||||
public bool AreRelated(int parentId, int childId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.ParentId == parentId && x.ChildId == childId);
|
||||
return _relationRepository.Get(query).Any();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two items are related with a given relation type alias
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the Parent relation</param>
|
||||
/// <param name="childId">Id of the Child relation</param>
|
||||
/// <param name="relationTypeAlias">Alias of the relation type</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exists with the given Ids and relation type, otherwise <c>False</c></returns>
|
||||
public bool AreRelated(int parentId, int childId, string relationTypeAlias)
|
||||
{
|
||||
var relType = GetRelationTypeByAlias(relationTypeAlias);
|
||||
if (relType == null)
|
||||
return false;
|
||||
|
||||
return AreRelated(parentId, childId, relType);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two items are related with a given relation type
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the Parent relation</param>
|
||||
/// <param name="childId">Id of the Child relation</param>
|
||||
/// <param name="relationType">Type of relation</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exists with the given Ids and relation type, otherwise <c>False</c></returns>
|
||||
public bool AreRelated(int parentId, int childId, IRelationType relationType)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id);
|
||||
return _relationRepository.Get(query).Any();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two items are related
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent entity</param>
|
||||
/// <param name="child">Child entity</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
|
||||
public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child)
|
||||
{
|
||||
return AreRelated(parent.Id, child.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two items are related
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent entity</param>
|
||||
/// <param name="child">Child entity</param>
|
||||
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
|
||||
public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias)
|
||||
{
|
||||
return AreRelated(parent.Id, child.Id, relationTypeAlias);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Saves a <see cref="Relation"/>
|
||||
/// </summary>
|
||||
/// <param name="relation">Relation to save</param>
|
||||
public void Save(IRelation relation)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IRelation>(relation);
|
||||
if (scope.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_relationRepository.Save(relation);
|
||||
scope.Complete();
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(SavedRelation, this, saveEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
/// <param name="relationType">RelationType to Save</param>
|
||||
public void Save(IRelationType relationType)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IRelationType>(relationType);
|
||||
if (scope.Events.DispatchCancelable(SavingRelationType, this, saveEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_relationTypeRepository.Save(relationType);
|
||||
scope.Complete();
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(SavedRelationType, this, saveEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <see cref="Relation"/>
|
||||
/// </summary>
|
||||
/// <param name="relation">Relation to Delete</param>
|
||||
public void Delete(IRelation relation)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<IRelation>(relation);
|
||||
if (scope.Events.DispatchCancelable(DeletingRelation, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_relationRepository.Delete(relation);
|
||||
scope.Complete();
|
||||
deleteEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(DeletedRelation, this, deleteEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
/// <param name="relationType">RelationType to Delete</param>
|
||||
public void Delete(IRelationType relationType)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var deleteEventArgs = new DeleteEventArgs<IRelationType>(relationType);
|
||||
if (scope.Events.DispatchCancelable(DeletingRelationType, this, deleteEventArgs))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
_relationTypeRepository.Delete(relationType);
|
||||
scope.Complete();
|
||||
deleteEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(DeletedRelationType, this, deleteEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all <see cref="Relation"/> objects based on the passed in <see cref="RelationType"/>
|
||||
/// </summary>
|
||||
/// <param name="relationType"><see cref="RelationType"/> to Delete Relations for</param>
|
||||
public void DeleteRelationsOfType(IRelationType relationType)
|
||||
{
|
||||
var relations = new List<IRelation>();
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var query = Query<IRelation>().Where(x => x.RelationTypeId == relationType.Id);
|
||||
relations.AddRange(_relationRepository.Get(query).ToList());
|
||||
|
||||
foreach (var relation in relations)
|
||||
_relationRepository.Delete(relation);
|
||||
|
||||
scope.Complete();
|
||||
|
||||
scope.Events.Dispatch(DeletedRelation, this, new DeleteEventArgs<IRelation>(relations, false));
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private IEnumerable<IRelation> GetRelationsByListOfTypeIds(IEnumerable<int> relationTypeIds)
|
||||
{
|
||||
var relations = new List<IRelation>();
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
foreach (var relationTypeId in relationTypeIds)
|
||||
{
|
||||
var id = relationTypeId;
|
||||
var query = Query<IRelation>().Where(x => x.RelationTypeId == id);
|
||||
relations.AddRange(_relationRepository.Get(query));
|
||||
}
|
||||
}
|
||||
return relations;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events Handlers
|
||||
/// <summary>
|
||||
/// Occurs before Deleting a Relation
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRelationService, DeleteEventArgs<IRelation>> DeletingRelation;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after a Relation is Deleted
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRelationService, DeleteEventArgs<IRelation>> DeletedRelation;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Saving a Relation
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRelationService, SaveEventArgs<IRelation>> SavingRelation;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after a Relation is Saved
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRelationService, SaveEventArgs<IRelation>> SavedRelation;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Deleting a RelationType
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRelationService, DeleteEventArgs<IRelationType>> DeletingRelationType;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after a RelationType is Deleted
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRelationService, DeleteEventArgs<IRelationType>> DeletedRelationType;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before Saving a RelationType
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRelationService, SaveEventArgs<IRelationType>> SavingRelationType;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after a RelationType is Saved
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<IRelationService, SaveEventArgs<IRelationType>> SavedRelationType;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a service that works on top of repositories.
|
||||
/// </summary>
|
||||
public abstract class RepositoryService : IService
|
||||
{
|
||||
protected ILogger Logger { get; }
|
||||
protected IEventMessagesFactory EventMessagesFactory { get; }
|
||||
protected IScopeProvider ScopeProvider { get; }
|
||||
|
||||
protected RepositoryService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory)
|
||||
{
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
EventMessagesFactory = eventMessagesFactory ?? throw new ArgumentNullException(nameof(eventMessagesFactory));
|
||||
ScopeProvider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||||
}
|
||||
|
||||
protected IQuery<T> Query<T>() => ScopeProvider.SqlContext.Query<T>();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
// fixme that one does not add anything = kill
|
||||
public abstract class ScopeRepositoryService : RepositoryService
|
||||
{
|
||||
protected ScopeRepositoryService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages server registrations in the database.
|
||||
/// </summary>
|
||||
public sealed class ServerRegistrationService : ScopeRepositoryService, IServerRegistrationService
|
||||
{
|
||||
private readonly IServerRegistrationRepository _serverRegistrationRepository;
|
||||
|
||||
private static readonly string CurrentServerIdentityValue = NetworkHelper.MachineName // eg DOMAIN\SERVER
|
||||
+ "/" + HttpRuntime.AppDomainAppId; // eg /LM/S3SVC/11/ROOT
|
||||
|
||||
private ServerRole _currentServerRole = ServerRole.Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerRegistrationService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="scopeProvider">A UnitOfWork provider.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <param name="eventMessagesFactory"></param>
|
||||
public ServerRegistrationService(IScopeProvider scopeProvider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
IServerRegistrationRepository serverRegistrationRepository)
|
||||
: base(scopeProvider, logger, eventMessagesFactory)
|
||||
{
|
||||
_serverRegistrationRepository = serverRegistrationRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Touches a server to mark it as active; deactivate stale servers.
|
||||
/// </summary>
|
||||
/// <param name="serverAddress">The server url.</param>
|
||||
/// <param name="serverIdentity">The server unique identity.</param>
|
||||
/// <param name="staleTimeout">The time after which a server is considered stale.</param>
|
||||
public void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.Servers);
|
||||
|
||||
((ServerRegistrationRepository) _serverRegistrationRepository).ClearCache(); // ensure we have up-to-date cache
|
||||
|
||||
var regs = _serverRegistrationRepository.GetMany().ToArray();
|
||||
var hasMaster = regs.Any(x => ((ServerRegistration) x).IsMaster);
|
||||
var server = regs.FirstOrDefault(x => x.ServerIdentity.InvariantEquals(serverIdentity));
|
||||
|
||||
if (server == null)
|
||||
{
|
||||
server = new ServerRegistration(serverAddress, serverIdentity, DateTime.Now);
|
||||
}
|
||||
else
|
||||
{
|
||||
server.ServerAddress = serverAddress; // should not really change but it might!
|
||||
server.UpdateDate = DateTime.Now;
|
||||
}
|
||||
|
||||
server.IsActive = true;
|
||||
if (hasMaster == false)
|
||||
server.IsMaster = true;
|
||||
|
||||
_serverRegistrationRepository.Save(server);
|
||||
_serverRegistrationRepository.DeactiveStaleServers(staleTimeout); // triggers a cache reload
|
||||
|
||||
// reload - cheap, cached
|
||||
// reload - cheap, cached
|
||||
|
||||
// default role is single server, but if registrations contain more
|
||||
// than one active server, then role is master or replica
|
||||
regs = _serverRegistrationRepository.GetMany().ToArray();
|
||||
|
||||
// default role is single server, but if registrations contain more
|
||||
// than one active server, then role is master or replica
|
||||
_currentServerRole = regs.Count(x => x.IsActive) > 1
|
||||
? (server.IsMaster ? ServerRole.Master : ServerRole.Replica)
|
||||
: ServerRole.Single;
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates a server.
|
||||
/// </summary>
|
||||
/// <param name="serverIdentity">The server unique identity.</param>
|
||||
public void DeactiveServer(string serverIdentity)
|
||||
{
|
||||
// because the repository caches "all" and has queries disabled...
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.Servers);
|
||||
|
||||
((ServerRegistrationRepository) _serverRegistrationRepository).ClearCache(); // ensure we have up-to-date cache // ensure we have up-to-date cache
|
||||
|
||||
var server = _serverRegistrationRepository.GetMany().FirstOrDefault(x => x.ServerIdentity.InvariantEquals(serverIdentity));
|
||||
if (server == null) return;
|
||||
server.IsActive = server.IsMaster = false;
|
||||
_serverRegistrationRepository.Save(server); // will trigger a cache reload // will trigger a cache reload
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates stale servers.
|
||||
/// </summary>
|
||||
/// <param name="staleTimeout">The time after which a server is considered stale.</param>
|
||||
public void DeactiveStaleServers(TimeSpan staleTimeout)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.Servers);
|
||||
_serverRegistrationRepository.DeactiveStaleServers(staleTimeout);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return all active servers.
|
||||
/// </summary>
|
||||
/// <param name="refresh">A value indicating whether to force-refresh the cache.</param>
|
||||
/// <returns>All active servers.</returns>
|
||||
/// <remarks>By default this method will rely on the repository's cache, which is updated each
|
||||
/// time the current server is touched, and the period depends on the configuration. Use the
|
||||
/// <paramref name="refresh"/> parameter to force a cache refresh and reload active servers
|
||||
/// from the database.</remarks>
|
||||
public IEnumerable<IServerRegistration> GetActiveServers(bool refresh = false)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.Servers);
|
||||
if (refresh) ((ServerRegistrationRepository) _serverRegistrationRepository).ClearCache();
|
||||
return _serverRegistrationRepository.GetMany().Where(x => x.IsActive).ToArray(); // fast, cached // fast, cached
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local server identity.
|
||||
/// </summary>
|
||||
public string CurrentServerIdentity => CurrentServerIdentityValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role of the current server.
|
||||
/// </summary>
|
||||
/// <returns>The role of the current server.</returns>
|
||||
public ServerRole GetCurrentServerRole()
|
||||
{
|
||||
return _currentServerRole;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & saved media or members
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If there is unpublished content with tags, those tags will not be contained
|
||||
/// </remarks>
|
||||
public class TagService : ScopeRepositoryService, ITagService
|
||||
{
|
||||
private readonly ITagRepository _tagRepository;
|
||||
|
||||
public TagService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory,
|
||||
ITagRepository tagRepository)
|
||||
: base(provider, logger, eventMessagesFactory)
|
||||
{
|
||||
_tagRepository = tagRepository;
|
||||
}
|
||||
|
||||
public TaggedEntity GetTaggedEntityById(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntityById(id);
|
||||
}
|
||||
}
|
||||
|
||||
public TaggedEntity GetTaggedEntityByKey(Guid key)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntityByKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Content by a specific 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Content, not the actual Content item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedContentByTagGroup(string tagGroup)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Content by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Content, not the actual Content item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedContentByTag(string tag, string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, tag, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Media by a specific 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Media, not the actual Media item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedMediaByTagGroup(string tagGroup)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Media, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Media by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Media, not the actual Media item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedMediaByTag(string tag, string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Media, tag, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Members by a specific 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Member, not the actual Member item.</remarks>
|
||||
/// <param name="tagGroup">Name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedMembersByTagGroup(string tagGroup)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Member, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets tagged Members by a specific 'Tag' and optional 'Tag Group'.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="TaggedEntity"/> contains the Id and Tags of the Member, not the actual Member item.</remarks>
|
||||
/// <param name="tag">Tag</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="TaggedEntity"/></returns>
|
||||
public IEnumerable<TaggedEntity> GetTaggedMembersByTag(string tag, string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Member, tag, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets every tag stored in the database
|
||||
/// </summary>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetAllTags(string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.All, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for content items
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetAllContentTags(string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Content, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for media items
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetAllMediaTags(string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Media, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags for member items
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetAllMemberTags(string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Member, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to a property by entity id
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="propertyTypeAlias">Property type alias</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to an entity (content, media or member) by entity id
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetTagsForEntity(int contentId, string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntity(contentId, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to a property by entity id
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="propertyTypeAlias">Property type alias</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetTagsForProperty(Guid contentId, string propertyTypeAlias, string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tags attached to an entity (content, media or member) by entity id
|
||||
/// </summary>
|
||||
/// <remarks>Use the optional tagGroup parameter to limit the
|
||||
/// result to a specific 'Tag Group'.</remarks>
|
||||
/// <param name="contentId">The content item id to get tags for</param>
|
||||
/// <param name="tagGroup">Optional name of the 'Tag Group'</param>
|
||||
/// <returns>An enumerable list of <see cref="ITag"/></returns>
|
||||
public IEnumerable<ITag> GetTagsForEntity(Guid contentId, string tagGroup = null)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _tagRepository.GetTagsForEntity(contentId, tagGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user