/// <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 = Constants.Security.SuperUserId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { // write-lock languages to guard against race conds when dealing with default language scope.WriteLock(Constants.Locks.Languages); EventMessages eventMessages = EventMessagesFactory.Get(); var deletingLanguageNotification = new LanguageDeletingNotification(language, eventMessages); if (scope.Notifications.PublishCancelable(deletingLanguageNotification)) { 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); scope.Notifications.Publish( new LanguageDeletedNotification(language, eventMessages).WithStateFrom(deletingLanguageNotification)); Audit(AuditType.Delete, "Delete Language", userId, language.Id, UmbracoObjectTypes.Language.GetName()); 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 (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.Servers); _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); scope.Complete(); } }
public void Save(TItem?item, int userId = Cms.Core.Constants.Security.SuperUserId) { if (item is null) { return; } using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { EventMessages eventMessages = EventMessagesFactory.Get(); SavingNotification <TItem> savingNotification = GetSavingNotification(item, eventMessages); if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; } if (string.IsNullOrWhiteSpace(item.Name)) { throw new ArgumentException("Cannot save item with empty name."); } if (item.Name != null && item.Name.Length > 255) { throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } 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 ContentTypeChange <TItem>[] changes = ComposeContentTypeChanges(item).ToArray(); // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); SavedNotification <TItem> savedNotification = GetSavedNotification(item, eventMessages); savedNotification.WithStateFrom(savingNotification); scope.Notifications.Publish(savedNotification); Audit(AuditType.Save, userId, item.Id); scope.Complete(); } }
public void Delete(IEnumerable <TItem> items, int userId = Cms.Core.Constants.Security.SuperUserId) { TItem[] itemsA = items.ToArray(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { EventMessages eventMessages = EventMessagesFactory.Get(); DeletingNotification <TItem> deletingNotification = GetDeletingNotification(itemsA, eventMessages); if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; } scope.WriteLock(WriteLockIds); // all descendants are going to be deleted TItem[] allDescendantsAndSelf = itemsA.SelectMany(xx => GetDescendants(xx.Id, true)).DistinctBy(x => x.Id).ToArray(); TItem[] 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 TItem[] 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 (TItem item in itemsA) { Repository.Delete(item); } ContentTypeChange <TItem>[] changes = allDescendantsAndSelf.Select(x => new ContentTypeChange <TItem>(x, ContentTypeChangeTypes.Remove)) .Concat(changed.Select(x => new ContentTypeChange <TItem>(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther))) .ToArray(); // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); DeletedNotification <TItem> deletedNotification = GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages); deletedNotification.WithStateFrom(deletingNotification); scope.Notifications.Publish(deletedNotification); Audit(AuditType.Delete, userId, -1); scope.Complete(); } }
public Attempt <OperationResult <MoveOperationStatusType>?> Move(TItem moving, int containerId) { EventMessages eventMessages = EventMessagesFactory.Get(); var moveInfo = new List <MoveEventInfo <TItem> >(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { var moveEventInfo = new MoveEventInfo <TItem>(moving, moving.Path, containerId); MovingNotification <TItem> movingNotification = GetMovingNotification(moveEventInfo, eventMessages); if (scope.Notifications.PublishCancelable(movingNotification)) { scope.Complete(); return(OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, eventMessages)); } 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, eventMessages)); } // 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. MovedNotification <TItem> movedNotification = GetMovedNotification(moveInfo, eventMessages); movedNotification.WithStateFrom(movingNotification); scope.Notifications.Publish(movedNotification); } return(OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages)); }
public void Save(IEnumerable <TItem> items, int userId = Cms.Core.Constants.Security.SuperUserId) { TItem[] itemsA = items.ToArray(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { EventMessages eventMessages = EventMessagesFactory.Get(); SavingNotification <TItem> savingNotification = GetSavingNotification(itemsA, eventMessages); if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; } scope.WriteLock(WriteLockIds); // all-or-nothing, validate them all first foreach (TItem contentType in itemsA) { ValidateLocked(contentType); // throws if invalid } foreach (TItem contentType in itemsA) { contentType.CreatorId = userId; if (contentType.Description == string.Empty) { contentType.Description = null; } Repository.Save(contentType); } // figure out impacted content types ContentTypeChange <TItem>[] changes = ComposeContentTypeChanges(itemsA).ToArray(); // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));; scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); SavedNotification <TItem> savedNotification = GetSavedNotification(itemsA, eventMessages); savedNotification.WithStateFrom(savingNotification); scope.Notifications.Publish(savedNotification); Audit(AuditType.Save, userId, -1); scope.Complete(); } }
/// <summary> /// Touches a server to mark it as active; deactivate stale servers. /// </summary> /// <param name="serverAddress">The server URL.</param> /// <param name="staleTimeout">The time after which a server is considered stale.</param> public void TouchServer(string serverAddress, TimeSpan staleTimeout) { var serverIdentity = GetCurrentServerIdentity(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.Servers); _serverRegistrationRepository.ClearCache(); // ensure we have up-to-date cache IServerRegistration[]? regs = _serverRegistrationRepository.GetMany()?.ToArray(); var hasSchedulingPublisher = regs?.Any(x => ((ServerRegistration)x).IsSchedulingPublisher); IServerRegistration?server = regs?.FirstOrDefault(x => x.ServerIdentity?.InvariantEquals(serverIdentity) ?? false); 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 (hasSchedulingPublisher == false) { server.IsSchedulingPublisher = true; } _serverRegistrationRepository.Save(server); _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); // triggers a cache reload // reload - cheap, cached regs = _serverRegistrationRepository.GetMany().ToArray(); // default role is single server, but if registrations contain more // than one active server, then role is scheduling publisher or subscriber _currentServerRole = regs.Count(x => x.IsActive) > 1 ? server.IsSchedulingPublisher ? ServerRole.SchedulingPublisher : ServerRole.Subscriber : ServerRole.Single; scope.Complete(); } }
public Attempt <OperationResult?> DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(WriteLockIds); // also for containers EntityContainer?container = _containerRepository?.Get(containerId); if (container == null) { return(OperationResult.Attempt.NoOperation(eventMessages)); } // 'container' here does not know about its children, so we need // to get it again from the entity repository, as a light entity IEntitySlim?entity = _entityRepository.Get(container.Id); if (entity?.HasChildren ?? false) { scope.Complete(); return(Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, eventMessages))); } var deletingNotification = new EntityContainerDeletingNotification(container, eventMessages); if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return(Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages))); } _containerRepository?.Delete(container); scope.Complete(); var deletedNotification = new EntityContainerDeletedNotification(container, eventMessages); deletedNotification.WithStateFrom(deletingNotification); scope.Notifications.Publish(deletedNotification); return(OperationResult.Attempt.Succeed(eventMessages)); // TODO: Audit trail ? }
public Attempt <OperationResult <OperationResultType, EntityContainer>?> RenameContainer(int id, string name, int userId = Cms.Core.Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(WriteLockIds); // also for containers try { EntityContainer?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 renamingNotification = new EntityContainerRenamingNotification(container, eventMessages); if (scope.Notifications.PublishCancelable(renamingNotification)) { scope.Complete(); return(OperationResult.Attempt.Cancel <EntityContainer>(eventMessages)); } _containerRepository?.Save(container); scope.Complete(); var renamedNotification = new EntityContainerRenamedNotification(container, eventMessages); renamedNotification.WithStateFrom(renamingNotification); scope.Notifications.Publish(renamedNotification); return(OperationResult.Attempt.Succeed(OperationResultType.Success, eventMessages, container)); } catch (Exception ex) { return(OperationResult.Attempt.Fail <EntityContainer>(eventMessages, ex)); } } }
public Attempt <OperationResult <OperationResultType, EntityContainer>?> CreateContainer(int parentId, Guid key, string name, int userId = Cms.Core.Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(WriteLockIds); // also for containers try { var container = new EntityContainer(ContainedObjectType) { Name = name, ParentId = parentId, CreatorId = userId, Key = key }; var savingNotification = new EntityContainerSavingNotification(container, eventMessages); if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return(OperationResult.Attempt.Cancel(eventMessages, container)); } _containerRepository?.Save(container); scope.Complete(); var savedNotification = new EntityContainerSavedNotification(container, eventMessages); savedNotification.WithStateFrom(savingNotification); scope.Notifications.Publish(savedNotification); // TODO: Audit trail ? return(OperationResult.Attempt.Succeed(eventMessages, container)); } catch (Exception ex) { scope.Complete(); return(OperationResult.Attempt.Fail <OperationResultType, EntityContainer>(OperationResultType.FailedCancelledByEvent, eventMessages, ex)); } } }
/// <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 = Constants.Security.SuperUserId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { // 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."); } } EventMessages eventMessages = EventMessagesFactory.Get(); var savingNotification = new LanguageSavingNotification(language, eventMessages); if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; } _languageRepository.Save(language); scope.Notifications.Publish( new LanguageSavedNotification(language, eventMessages).WithStateFrom(savingNotification)); Audit(AuditType.Save, "Save Language", userId, language.Id, UmbracoObjectTypes.Language.GetName()); scope.Complete(); } }
public Attempt <OperationResult?> SaveContainer(EntityContainer container, int userId = Cms.Core.Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); Guid containerObjectType = ContainerObjectType; if (container.ContainerObjectType != containerObjectType) { var ex = new InvalidOperationException("Not a container of the proper type."); return(OperationResult.Attempt.Fail(eventMessages, 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(eventMessages, ex)); } using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { var savingNotification = new EntityContainerSavingNotification(container, eventMessages); if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return(OperationResult.Attempt.Cancel(eventMessages)); } scope.WriteLock(WriteLockIds); // also for containers _containerRepository?.Save(container); scope.Complete(); var savedNotification = new EntityContainerSavedNotification(container, eventMessages); savedNotification.WithStateFrom(savingNotification); scope.Notifications.Publish(savedNotification); } // TODO: Audit trail ? return(OperationResult.Attempt.Succeed(eventMessages)); }
/// <inheritdoc /> public bool TrySetValue(string key, string originalValue, string newValue) { using (ICoreScope scope = _scopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.KeyValues); IKeyValue?keyValue = _repository.Get(key); if (keyValue == null || keyValue.Value != originalValue) { return(false); } keyValue.Value = newValue; keyValue.UpdateDate = DateTime.Now; _repository.Save(keyValue); scope.Complete(); } return(true); }
/// <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 (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.Servers); _serverRegistrationRepository .ClearCache(); // ensure we have up-to-date cache // ensure we have up-to-date cache IServerRegistration?server = _serverRegistrationRepository.GetMany() ?.FirstOrDefault(x => x.ServerIdentity?.InvariantEquals(serverIdentity) ?? false); if (server == null) { return; } server.IsActive = server.IsSchedulingPublisher = false; _serverRegistrationRepository.Save(server); // will trigger a cache reload // will trigger a cache reload scope.Complete(); } }
/// <inheritdoc /> public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1) { using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) { scope.WriteLock(Constants.Locks.ContentTree); _documentVersionRepository.SetPreventCleanup(versionId, preventCleanup); ContentVersionMeta?version = _documentVersionRepository.Get(versionId); if (version is null) { return; } AuditType auditType = preventCleanup ? AuditType.ContentVersionPreventCleanup : AuditType.ContentVersionEnableCleanup; var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'"; Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}"); } }
/// <inheritdoc /> public void SetValue(string key, string value) { using (ICoreScope scope = _scopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.KeyValues); IKeyValue?keyValue = _repository.Get(key); if (keyValue == null) { keyValue = new KeyValue { Identifier = key, Value = value, UpdateDate = DateTime.Now }; } else { keyValue.Value = value; keyValue.UpdateDate = DateTime.Now; } _repository.Save(keyValue); scope.Complete(); } }
public Attempt <OperationResult <MoveOperationStatusType, TItem>?> Copy(TItem copying, int containerId) { EventMessages eventMessages = EventMessagesFactory.Get(); TItem copy; using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(WriteLockIds); try { if (containerId > 0) { EntityContainer?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)(object)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) { TItem?parent = Repository.Get(copy.ParentId); if (parent != null) { copy.RemoveContentType(parent.Alias); } } copy.ParentId = containerId; SavingNotification <TItem> savingNotification = GetSavingNotification(copy, eventMessages); if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return(OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, eventMessages, copy)); } Repository.Save(copy); ContentTypeChange <TItem>[] changes = ComposeContentTypeChanges(copy).ToArray(); _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); SavedNotification <TItem> savedNotification = GetSavedNotification(copy, eventMessages); savedNotification.WithStateFrom(savingNotification); scope.Notifications.Publish(savedNotification); scope.Complete(); } catch (DataOperationException <MoveOperationStatusType> ex) { return(OperationResult.Attempt.Fail <MoveOperationStatusType, TItem>(ex.Operation, eventMessages)); // causes rollback } } return(OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages, copy)); }
private IReadOnlyCollection <ContentVersionMeta> CleanupDocumentVersions(DateTime asAtDate) { List <ContentVersionMeta> versionsToDelete; /* Why so many scopes? * * We could just work out the set to delete at SQL infra level which was the original plan, however we agreed that really we should fire * ContentService.DeletingVersions so people can hook & cancel if required. * * On first time run of cleanup on a site with a lot of history there may be a lot of historic ContentVersions to remove e.g. 200K for our.umbraco.com. * If we weren't supporting SQL CE we could do TVP, or use temp tables to bulk delete with joins to our list of version ids to nuke. * (much nicer, we can kill 100k in sub second time-frames). * * However we are supporting SQL CE, so the easiest thing to do is use the Umbraco InGroupsOf helper to create a query with 2K args of version * ids to delete at a time. * * This is already done at the repository level, however if we only had a single scope at service level we're still locking * the ContentVersions table (and other related tables) for a couple of minutes which makes the back office unusable. * * As a quick fix, we can also use InGroupsOf at service level, create a scope per group to give other connections a chance * to grab the locks and execute their queries. * * This makes the back office a tiny bit sluggish during first run but it is usable for loading tree and publishing content. * * There are optimizations we can do, we could add a bulk delete for SqlServerSyntaxProvider which differs in implementation * and fallback to this naive approach only for SQL CE, however we agreed it is not worth the effort as this is a one time pain, * subsequent runs shouldn't have huge numbers of versions to cleanup. * * tl;dr lots of scopes to enable other connections to use the DB whilst we work. */ using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) { IReadOnlyCollection <ContentVersionMeta>?allHistoricVersions = _documentVersionRepository.GetDocumentVersionsEligibleForCleanup(); if (allHistoricVersions is null) { return(Array.Empty <ContentVersionMeta>()); } _logger.LogDebug("Discovered {count} candidate(s) for ContentVersion cleanup", allHistoricVersions.Count); versionsToDelete = new List <ContentVersionMeta>(allHistoricVersions.Count); IEnumerable <ContentVersionMeta> filteredContentVersions = _contentVersionCleanupPolicy.Apply(asAtDate, allHistoricVersions); foreach (ContentVersionMeta version in filteredContentVersions) { EventMessages messages = _eventMessagesFactory.Get(); if (scope.Notifications.PublishCancelable(new ContentDeletingVersionsNotification(version.ContentId, messages, version.VersionId))) { _logger.LogDebug("Delete cancelled for ContentVersion [{versionId}]", version.VersionId); continue; } versionsToDelete.Add(version); } } if (!versionsToDelete.Any()) { _logger.LogDebug("No remaining ContentVersions for cleanup"); return(Array.Empty <ContentVersionMeta>()); } _logger.LogDebug("Removing {count} ContentVersion(s)", versionsToDelete.Count); foreach (IEnumerable <ContentVersionMeta> group in versionsToDelete.InGroupsOf(Constants.Sql.MaxParameterCount)) { using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) { scope.WriteLock(Constants.Locks.ContentTree); var groupEnumerated = group.ToList(); _documentVersionRepository.DeleteVersions(groupEnumerated.Select(x => x.VersionId)); foreach (ContentVersionMeta version in groupEnumerated) { EventMessages messages = _eventMessagesFactory.Get(); scope.Notifications.Publish(new ContentDeletedVersionsNotification(version.ContentId, messages, version.VersionId)); } } } using (_scopeProvider.CreateCoreScope(autoComplete: true)) { Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy"); } return(versionsToDelete); }
public void Delete(TItem item, int userId = Cms.Core.Constants.Security.SuperUserId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { EventMessages eventMessages = EventMessagesFactory.Get(); DeletingNotification <TItem> deletingNotification = GetDeletingNotification(item, eventMessages); if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; } scope.WriteLock(WriteLockIds); // all descendants are going to be deleted TItem[] descendantsAndSelf = GetDescendants(item.Id, true) .ToArray(); TItem[] 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 TItem[] changed = descendantsAndSelf.SelectMany(xx => GetComposedOf(xx.Id)) .Distinct() .Except(descendantsAndSelf) .ToArray(); // delete content DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id)); // Next find all other document types that have a reference to this content type IEnumerable <TItem> referenceToAllowedContentTypes = GetAll().Where(q => q.AllowedContentTypes?.Any(p => p.Id.Value == item.Id) ?? false); foreach (TItem reference in referenceToAllowedContentTypes) { reference.AllowedContentTypes = reference.AllowedContentTypes?.Where(p => p.Id.Value != item.Id); var changedRef = new List <ContentTypeChange <TItem> >() { new ContentTypeChange <TItem>(reference, ContentTypeChangeTypes.RefreshMain) }; // Fire change event scope.Notifications.Publish(GetContentTypeChangedNotification(changedRef, eventMessages)); } // 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); ContentTypeChange <TItem>[] changes = descendantsAndSelf.Select(x => new ContentTypeChange <TItem>(x, ContentTypeChangeTypes.Remove)) .Concat(changed.Select(x => new ContentTypeChange <TItem>(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther))) .ToArray(); // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); DeletedNotification <TItem> deletedNotification = GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages); deletedNotification.WithStateFrom(deletingNotification); scope.Notifications.Publish(deletedNotification); Audit(AuditType.Delete, userId, item.Id); scope.Complete(); } }