Revert "Temp8 tinymce"

This commit is contained in:
Warren Buckley
2018-11-22 14:05:51 +00:00
committed by GitHub
parent 2a0748fc1e
commit 54a2aa00a7
6677 changed files with 646351 additions and 410535 deletions

View File

@@ -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();
}
}
}
}

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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)
{ }
}
}

View File

@@ -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));
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}
}
}

View File

@@ -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("&nbsp;", " ");
oldString = oldString.Replace("&rsquo;", "'");
oldString = oldString.Replace("&amp;", "&");
oldString = oldString.Replace("&ldquo;", "“");
oldString = oldString.Replace("&rdquo;", "”");
oldString = oldString.Replace("&quot;", "\"");
}
// 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

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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>();
}
}

View File

@@ -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)
{ }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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