Remove broken entity references

This removes references from nodes to entities which don't exist anymore.

@param string $workspaceName
@return void
@throws NodeException
@throws PropertyException
@throws SecurityException
1723
public function removeBrokenEntityReferences($workspaceName)
    {
        $this->dispatch(self::EVENT_NOTICE, 'Checking for broken entity references ...');

        /** @var \Neos\ContentRepository\Domain\Model\Workspace $workspace */
        $workspace = $this->workspaceRepository->findByIdentifier($workspaceName);

        $nodeTypesWithEntityReferences = [];
        foreach ($this->nodeTypeManager->getNodeTypes() as $nodeType) {
            /** @var NodeType $nodeType */
            foreach (array_keys($nodeType->getProperties()) as $propertyName) {
                $propertyType = $nodeType->getPropertyType($propertyName);
                if (strpos($propertyType, '\\') !== false) {
                    if (!isset($nodeTypesWithEntityReferences[$nodeType->getName()])) {
                        $nodeTypesWithEntityReferences[$nodeType->getName()] = [];
                    }
                    $nodeTypesWithEntityReferences[$nodeType->getName()][$propertyName] = $propertyType;
                }
            }
        }

        $nodesWithBrokenEntityReferences = [];
        $brokenReferencesCount = 0;
        foreach ($nodeTypesWithEntityReferences as $nodeTypeName => $properties) {
            $nodeDatas = $this->nodeDataRepository->findByParentAndNodeTypeRecursively('/', $nodeTypeName, $workspace);
            foreach ($nodeDatas as $nodeData) {
                /** @var NodeData $nodeData */
                foreach ($properties as $propertyName => $propertyType) {
                    $propertyValue = $nodeData->getProperty($propertyName);
                    $convertedProperty = null;

                    if (is_object($propertyValue)) {
                        $convertedProperty = $propertyValue;
                    }
                    if (is_string($propertyValue) && strlen($propertyValue) === 36) {
                        $convertedProperty = $this->propertyMapper->convert($propertyValue, $propertyType);
                        if ($convertedProperty === null) {
                            $nodesWithBrokenEntityReferences[$nodeData->getIdentifier()][$propertyName] = $nodeData;
                            $this->dispatch(self::EVENT_NOTICE, sprintf('Broken reference in "<i>%s</i>", property "<i>%s</i>" (<i>%s</i>) referring to <i>%s</i>.', $nodeData->getPath(), $nodeData->getIdentifier(), $propertyName, $propertyType, $propertyValue));
                            $brokenReferencesCount ++;
                        }
                    }
                    if ($convertedProperty instanceof Proxy) {
                        try {
                            $convertedProperty->__load();
                        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (EntityNotFoundException $e) {
                            $nodesWithBrokenEntityReferences[$nodeData->getIdentifier()][$propertyName] = $nodeData;
                            $this->dispatch(self::EVENT_NOTICE, sprintf('Broken reference in "<i>%s</i>", property "<i>%s</i>" (<i>%s</i>) referring to <i>%s</i>.', $nodeData->getPath(), $nodeData->getIdentifier(), $propertyName, $propertyType, $propertyValue));
                            $brokenReferencesCount ++;
                        }
                    }
                }
            }
        }

        if ($brokenReferencesCount === 0) {
            return;
        }
        $taskDescription = sprintf('Remove <i>%d</i> broken entity reference%s.', $brokenReferencesCount, $brokenReferencesCount > 1 ? 's' : '');
        $taskClosure = function () use ($nodesWithBrokenEntityReferences) {
            foreach ($nodesWithBrokenEntityReferences as $nodeIdentifier => $properties) {
                foreach ($properties as $propertyName => $nodeData) {
                    /** @var NodeData $nodeData */
                    $nodeData->setProperty($propertyName, null);
                }
            }
            $this->persistenceManager->persistAll();
        };
        $taskRequiresConfirmation = true;
        $this->dispatch(self::EVENT_TASK, $taskDescription, $taskClosure, $taskRequiresConfirmation);
    }
701943
public function onWorkspacesDelete(GenericDatasEvent $event)
    {
        $workspaces = $event->getDatas();
        $ids = array();

        foreach ($workspaces as $workspace) {
            $ids[] = $workspace->getId();
        }
        $this->tagManager->removeTaggedObjectsByClassAndIds(
            'Claroline\CoreBundle\Entity\Workspace\Workspace',
            $ids
        );
    }
1238168
protected function removeDisallowedChildNodes($workspaceName)
    {
        $this->dispatch(self::EVENT_NOTICE, 'Checking for disallowed child nodes ...');

        /** @var \Neos\ContentRepository\Domain\Model\Workspace $workspace */
        $workspace = $this->workspaceRepository->findByIdentifier($workspaceName);

        $nodes = [];
        $nodeExceptionCount = 0;
        $removeDisallowedChildNodes = function (NodeInterface $node) use (&$removeDisallowedChildNodes, &$nodes, &$nodeExceptionCount) {
            try {
                foreach ($node->getChildNodes() as $childNode) {
                    /** @var $childNode NodeInterface */
                    if (!$childNode->isAutoCreated() && !$node->isNodeTypeAllowedAsChildNode($childNode->getNodeType())) {
                        $nodes[] = $childNode;
                        $parent = $node->isAutoCreated() ? $node->getParent() : $node;
                        $this->dispatch(self::EVENT_NOTICE, sprintf('Found disallowed node named "<i>%s</i>" (<i>%s</i>) in "<i>%s</i>", child of node "<i>%s</i>" (<i>%s</i>)', $childNode->getName(), $childNode->getNodeType()->getName(), $childNode->getPath(), $parent->getName(), $parent->getNodeType()->getName()));
                    } else {
                        $removeDisallowedChildNodes($childNode);
                    }
                }
            } catch (\Exception $exception) {
                $this->dispatch(self::EVENT_WARNING, sprintf('Error while traversing child nodes of node <i>%s</i>: %s (%s)', $node->getIdentifier(), $exception->getMessage(), $exception->getCode()));
                $nodeExceptionCount++;
            }
        };

        // TODO: Performance could be improved by a search for all child node data instead of looping over all contexts
        foreach ($this->contentDimensionCombinator->getAllAllowedCombinations() as $dimensionConfiguration) {
            $context = $this->createContext($workspace->getName(), $dimensionConfiguration);
            $removeDisallowedChildNodes($context->getRootNode());
            $context->getFirstLevelNodeCache()->flush();
            $this->nodeFactory->reset();
        }

        $disallowedChildNodesCount = count($nodes);
        if ($disallowedChildNodesCount > 0) {
            $taskDescription = sprintf('Remove <i>%d</i> disallowed node%s.', $disallowedChildNodesCount, $disallowedChildNodesCount > 1 ? 's' : '');
            $taskClosure = function () use ($nodes, $workspaceName) {
                foreach ($nodes as $node) {
                    $this->removeNodeAndChildNodesInWorkspaceByPath($node->getPath(), $workspaceName);
                }
            };
            $taskRequiresConfirmation = true;
            $this->dispatch(self::EVENT_TASK, $taskDescription, $taskClosure, $taskRequiresConfirmation);

            if ($nodeExceptionCount > 0) {
                $this->dispatch(self::EVENT_NOTICE, '<i>%d</i> error%s occurred during child node traversing.', $nodeExceptionCount, $nodeExceptionCount > 1 ? 's' : '');
            }
        }
    }
283449
public void clear(final String propertyName) {
        Preconditions.checkNotNull(propertyName);
        if (properties.containsKey(propertyName)) {
            properties.put(propertyName, null);
        } else if (references.containsKey(propertyName)) {
            references.put(propertyName, null);
        }
    }
283693
public function fixNodesWithInconsistentIdentifier()
    {
        $this->dispatch(self::EVENT_NOTICE, 'Checking for nodes with inconsistent identifier ...');

        $nodesArray = [];
        $liveWorkspaceNames = [];
        $nonLiveWorkspaceNames = [];
        foreach ($this->workspaceRepository->findAll() as $workspace) {
            /** @var Workspace $workspace */
            if ($workspace->getBaseWorkspace() !== null) {
                $nonLiveWorkspaceNames[] = $workspace->getName();
            } else {
                $liveWorkspaceNames[] = $workspace->getName();
            }
        }

        foreach ($nonLiveWorkspaceNames as $workspaceName) {
            /** @var QueryBuilder $queryBuilder */
            $queryBuilder = $this->entityManager->createQueryBuilder();
            $queryBuilder->select('nonlive.Persistence_Object_Identifier, nonlive.identifier, nonlive.path, live.identifier AS liveIdentifier')
                ->from(NodeData::class, 'nonlive')
                ->join(NodeData::class, 'live', 'WITH', 'live.path = nonlive.path AND live.dimensionsHash = nonlive.dimensionsHash AND live.identifier != nonlive.identifier')
                ->where('nonlive.workspace = ?1')
                ->andWhere($queryBuilder->expr()->in('live.workspace', $liveWorkspaceNames))
                ->andWhere('nonlive.path != \'/\'')
                ->setParameter(1, $workspaceName)
            ;

            foreach ($queryBuilder->getQuery()->getArrayResult() as $nodeDataArray) {
                $this->dispatch(self::EVENT_NOTICE, sprintf('Node <i>%s</i> in workspace <i>%s</i> has identifier <i>%s</i> but live node has identifier <i>%s</i>.', $nodeDataArray['path'], $workspaceName, $nodeDataArray['identifier'], $nodeDataArray['liveIdentifier']));
                $nodesArray[] = $nodeDataArray;
            }
        }

        if ($nodesArray === []) {
            return;
        }

        $numberOfNodes = count($nodesArray);
        $taskDescription = sprintf('Fix identifier%s of %s node%s', $numberOfNodes > 1 ? 's' : '', $numberOfNodes, $numberOfNodes > 1 ? 's' : '');
        $taskClosure = function () use ($nodesArray) {
            foreach ($nodesArray as $nodeArray) {
                /** @var QueryBuilder $queryBuilder */
                $queryBuilder = $this->entityManager->createQueryBuilder();
                $queryBuilder->update(NodeData::class, 'nonlive')
                    ->set('nonlive.identifier', $queryBuilder->expr()->literal($nodeArray['liveIdentifier']))
                    ->where('nonlive.Persistence_Object_Identifier = ?1')
                    ->setParameter(1, $nodeArray['Persistence_Object_Identifier']);
                $result = $queryBuilder->getQuery()->getResult();
                if ($result !== 1) {
                    $errorMessage = 'The update query returned an unexpected result!' . PHP_EOL;
                    $errorMessage .= sprintf('<b>Query:</b> %s', $queryBuilder->getQuery()->getSQL()) . PHP_EOL;
                    $errorMessage .= sprintf('<b>Result:</b> %s', var_export($result, true));
                    $this->dispatch(self::EVENT_ERROR, $errorMessage);
                    return;
                }
            }
        };
        $taskRequiresConfirmation = true;
        $this->dispatch(self::EVENT_TASK, $taskDescription, $taskClosure, $taskRequiresConfirmation);
    }
393471
public function removeByName($propertyName)
    {
        $removedList = new self();
        $list = $this->getIterator();
        foreach ($list as $index => $property) {
            if ((is_array($propertyName) && in_array($property->getName(), $propertyName))
                || ($property->getName() === $propertyName)
            ) {
                $removedList->add(clone $property);
                $this->remove($index);
            }
        }

        return $removedList;
    }
258497
private function removeLocalizedNodeProperties(NodeInterface $node, $locale)
    {
        // remove all localized system properties from the node
        foreach ($node->getProperties($this->propertyEncoder->localizedSystemName('', $locale) . '*') as $property) {
            $property->remove();
        }

        // remove all localized content properties from the node
        foreach ($node->getProperties($this->propertyEncoder->localizedContentName('', $locale) . '*') as $property) {
            $property->remove();
        }
    }
499920
protected void doDelete(final ItemData item, final WorkspaceStorageConnection con, ChangedSizeHandler sizeHandler)
      throws RepositoryException, InvalidItemStateException
   {

      if (item.isNode())
      {
         con.delete((NodeData)item);
      }
      else
      {
         con.delete((PropertyData)item, sizeHandler);
      }
   }
1094993
public function clear(string $entityNamespace)
    {
        $entityNamespace = $this->locateEntity($entityNamespace);
        $this
            ->getObjectManager($entityNamespace)
            ->clear($entityNamespace);
    }
927569
private function removeNonSatNSNode(string $namespace)
    {
        foreach ($this->dom()->getElementsByTagNameNS($namespace, '*') as $children) {
            $children->parentNode->removeChild($children);
        }
    }
1049900
public void clearNamespaces( String elementName ) {
      if( StringUtils.isBlank( elementName ) ){
         rootNamespace.clear();
      }else{
         namespacesPerElement.remove( elementName );
      }
   }
1223835
protected function createContext($workspaceName)
    {
        $contextProperties = [
            'workspaceName' => $workspaceName,
            'invisibleContentShown' => true,
            'inaccessibleContentShown' => true
        ];

        $currentDomain = $this->domainRepository->findOneByActiveRequest();

        if ($currentDomain !== null) {
            $contextProperties['currentSite'] = $currentDomain->getSite();
            $contextProperties['currentDomain'] = $currentDomain;
        } else {
            $contextProperties['currentSite'] = $this->siteRepository->findFirstOnline();
        }

        return $this->contextFactory->create($contextProperties);
    }
823355
protected function getNodeDataByNodeTypeAndWorkspace($nodeType, $workspaceName)
    {
        /** @var QueryBuilder $queryBuilder */
        $queryBuilder = $this->entityManager->createQueryBuilder();

        $queryBuilder->select('n')
            ->distinct()
            ->from(NodeData::class, 'n')
            ->where('n.nodeType = :nodeType')
            ->andWhere('n.workspace = :workspace')
            ->andWhere('n.movedTo IS NULL OR n.removed = :removed')
            ->setParameter('nodeType', $nodeType)
            ->setParameter('workspace', $workspaceName)
            ->setParameter('removed', false, \PDO::PARAM_BOOL);
        return $queryBuilder->getQuery()->getResult();
    }
186626
protected function prepareContextProperties($workspaceName, array $dimensions = null)
    {
        $contextProperties = [
            'workspaceName' => $workspaceName,
            'invisibleContentShown' => false,
            'removedContentShown' => false
        ];

        if ($workspaceName !== 'live') {
            $contextProperties['invisibleContentShown'] = true;
        }

        if ($dimensions !== null) {
            $contextProperties['dimensions'] = $dimensions;
        }

        return $contextProperties;
    }
680566
public function removeRegistration(WorkspaceRegistrationQueue $workspaceRegistration)
    {
        $this->dispatcher->dispatch(
            'log',
            'Log\LogWorkspaceRegistrationDecline',
            [$workspaceRegistration]
        );

        $this->om->remove($workspaceRegistration);
        $this->om->flush();
    }
649542
private function clearEntities()
    {
        $this->nodeEntities = array();
        $this->relationEntities = array();

        $this->nodeEntitiesToRemove = array();
        $this->relationEntitiesToRemove = array();

        $this->startNodeRelations = array();
        $this->endNodeRelations = array();
    }
90510
public function clear($documentName = null)
    {
        if ($documentName === null) {
            $this->identityMap =
            $this->documentIdentifiers =
            $this->originalDocumentData =
            $this->documentChangeSets =
            $this->documentStates =
            $this->scheduledForDirtyCheck =
            $this->documentInsertions =
            $this->documentUpserts =
            $this->documentUpdates =
            $this->documentDeletions =
            $this->extraUpdates =
            $this->collectionUpdates =
            $this->collectionDeletions =
            $this->parentAssociations =
            $this->orphanRemovals = array();

            if ($this->commitOrderCalculator !== null) {
                $this->commitOrderCalculator->clear();
            }
        } else {
            $visited = array();
            foreach ($this->identityMap as $className => $documents) {
                if ($className === $documentName) {
                    foreach ($documents as $document) {
                        $this->doDetach($document, $visited, true);
                    }
                }
            }
        }

        if ($this->evm->hasListeners(Events::onClear)) {
            $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->dm, $documentName));
        }
    }