Ejemplo n.º 1
0
    /// <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();
        }
    }
Ejemplo n.º 2
0
 /// <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();
            }
        }
Ejemplo n.º 7
0
    /// <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));
                }
            }
        }
Ejemplo n.º 11
0
    /// <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));
        }
Ejemplo n.º 13
0
    /// <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);
    }
Ejemplo n.º 14
0
    /// <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();
        }
    }
Ejemplo n.º 15
0
        /// <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}");
            }
        }
Ejemplo n.º 16
0
    /// <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));
    }
Ejemplo n.º 18
0
        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();
            }
        }