/** * @test */ public function matchesWorkspaceAndDimensionsWithMatchingWorkspaceAndDimensionsReturnsTrue() { $this->nodeData = new NodeData('/foo/bar', $this->mockWorkspace, null, array('language' => array('mul_ZZ'))); $this->mockWorkspace->expects($this->any())->method('getName')->will($this->returnValue('live')); $result = $this->nodeData->matchesWorkspaceAndDimensions($this->mockWorkspace, array('language' => array('de_DE', 'mul_ZZ'))); $this->assertTrue($result); }
/** * @test */ public function publishNodePublishesTheNodeAndItsChildNodeCollectionsIfTheNodeTypeHasChildNodes() { $mockNode = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Model\\NodeInterface')->getMock(); $mockChildNode = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Model\\NodeInterface')->getMock(); $mockNodeType = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Model\\NodeType')->disableOriginalConstructor()->setMethods(array('hasConfiguration', 'isOfType'))->getMock(); $mockNodeType->expects($this->atLeastOnce())->method('hasConfiguration')->with('childNodes')->will($this->returnValue(true)); $mockNode->expects($this->atLeastOnce())->method('getNodeType')->will($this->returnValue($mockNodeType)); $mockNode->expects($this->atLeastOnce())->method('getWorkspace')->will($this->returnValue($this->mockWorkspace)); $mockNode->expects($this->atLeastOnce())->method('getChildNodes')->with('TYPO3.Neos:ContentCollection')->will($this->returnValue(array($mockChildNode))); $mockTargetWorkspace = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Model\\Workspace')->disableOriginalConstructor()->getMock(); $this->mockWorkspace->expects($this->atLeastOnce())->method('publishNodes')->with(array($mockNode, $mockChildNode), $mockTargetWorkspace); $this->publishingService->publishNode($mockNode, $mockTargetWorkspace); }
/** * This test checks that targets for resource links are correctly replaced * * @test */ public function evaluateReplaceResourceLinkTargets() { $assetIdentifier = 'aeabe76a-551a-495f-a324-ad9a86b2aff8'; $resourceLinkTarget = '_blank'; $value = 'This string contains two asset links and an external link: one with a target set <a target="top" href="asset://' . $assetIdentifier . '">example</a> and one without a target <a href="asset://' . $assetIdentifier . '">example2</a> and an external link <a href="http://www.example.org">example3</a>'; $this->addValueExpectation($value, null, false, null, $resourceLinkTarget); $this->mockWorkspace->expects($this->any())->method('getName')->will($this->returnValue('live')); $self = $this; $this->mockLinkingService->expects($this->atLeastOnce())->method('resolveAssetUri')->will($this->returnCallback(function ($assetUri) use($self, $assetIdentifier) { if ($assetUri !== 'asset://' . $assetIdentifier) { $self->fail('Unexpected asset URI "' . $assetUri . '"'); } return 'http://localhost/_Resources/01'; })); $expectedResult = 'This string contains two asset links and an external link: one with a target set <a target="' . $resourceLinkTarget . '" href="http://localhost/_Resources/01">example</a> and one without a target <a target="' . $resourceLinkTarget . '" href="http://localhost/_Resources/01">example2</a> and an external link <a href="http://www.example.org">example3</a>'; $actualResult = $this->convertUrisImplementation->evaluate(); $this->assertSame($expectedResult, $actualResult); }
/** * Find a single node by exact path. * * @param string $path Absolute path of the node * @param Workspace $workspace The containing workspace * @param array $dimensions An array of dimensions with array of ordered values to use for fallback matching * @param boolean|NULL $removedNodes Include removed nodes, NULL (all), FALSE (no removed nodes) or TRUE (only removed nodes) * @throws \InvalidArgumentException * @return NodeData The matching node if found, otherwise NULL */ public function findOneByPath($path, Workspace $workspace, array $dimensions = null, $removedNodes = false) { $path = strtolower($path); if ($path === '' || $path !== '/' && ($path[0] !== '/' || substr($path, -1, 1) === '/')) { throw new \InvalidArgumentException('"' . $path . '" is not a valid path: must start but not end with a slash.', 1284985489); } if ($path === '/') { return $workspace->getRootNodeData(); } $workspaces = array(); while ($workspace !== null) { /** @var $node NodeData */ foreach ($this->addedNodes as $node) { if ($node->getPath() === $path && $node->matchesWorkspaceAndDimensions($workspace, $dimensions)) { return $node; } } foreach ($this->removedNodes as $node) { if ($node->getPath() === $path && $node->matchesWorkspaceAndDimensions($workspace, $dimensions)) { return null; } } $workspaces[] = $workspace; $workspace = $workspace->getBaseWorkspace(); } $queryBuilder = $this->createQueryBuilder($workspaces); if ($dimensions !== null) { $this->addDimensionJoinConstraintsToQueryBuilder($queryBuilder, $dimensions); } else { $dimensions = array(); } $this->addPathConstraintToQueryBuilder($queryBuilder, $path); $query = $queryBuilder->getQuery(); $nodes = $query->getResult(); $foundNodes = $this->reduceNodeVariantsByWorkspacesAndDimensions($nodes, $workspaces, $dimensions); $foundNodes = $this->filterRemovedNodes($foundNodes, $removedNodes); if ($foundNodes !== array()) { return reset($foundNodes); } return null; }
/** * Creates a new content context based on the given workspace and the NodeData object. * * @param Workspace $workspace Workspace for the new context * @param array $dimensionValues The dimension values for the new context * @param array $contextProperties Additional pre-defined context properties * @return Context */ protected function createContext(Workspace $workspace, array $dimensionValues, array $contextProperties = array()) { $contextProperties += array('workspaceName' => $workspace->getName(), 'inaccessibleContentShown' => TRUE, 'invisibleContentShown' => TRUE, 'removedContentShown' => TRUE, 'dimensions' => $dimensionValues); return $this->contextFactory->create($contextProperties); }
/** * Collects all nodes with missing shadow nodes * * @param Workspace $workspace * @param boolean $dryRun * @param NodeType $nodeType * @return array */ protected function fixShadowNodesInWorkspace(Workspace $workspace, $dryRun, NodeType $nodeType = null) { $workspaces = array_merge([$workspace], $workspace->getBaseWorkspaces()); $fixedShadowNodes = 0; foreach ($workspaces as $workspace) { /** @var Workspace $workspace */ if ($workspace->getBaseWorkspace() === null) { continue; } /** @var QueryBuilder $queryBuilder */ $queryBuilder = $this->entityManager->createQueryBuilder(); $queryBuilder->select('n')->from(NodeData::class, 'n')->where('n.workspace = :workspace'); $queryBuilder->setParameter('workspace', $workspace->getName()); if ($nodeType !== null) { $queryBuilder->andWhere('n.nodeType = :nodeType'); $queryBuilder->setParameter('nodeType', $nodeType->getName()); } /** @var NodeData $nodeData */ foreach ($queryBuilder->getQuery()->getResult() as $nodeData) { $nodeDataSeenFromParentWorkspace = $this->nodeDataRepository->findOneByIdentifier($nodeData->getIdentifier(), $workspace->getBaseWorkspace(), $nodeData->getDimensionValues()); // This is the good case, either the node does not exist or was shadowed if ($nodeDataSeenFromParentWorkspace === null) { continue; } // Also good, the node was not moved at all. if ($nodeDataSeenFromParentWorkspace->getPath() === $nodeData->getPath()) { continue; } $nodeDataOnSamePath = $this->nodeDataRepository->findOneByPath($nodeData->getPath(), $workspace->getBaseWorkspace(), $nodeData->getDimensionValues(), null); // We cannot just put a shadow node in the path, something exists, but that should be fine. if ($nodeDataOnSamePath !== null) { continue; } if (!$dryRun) { $nodeData->createShadow($nodeDataSeenFromParentWorkspace->getPath()); } $fixedShadowNodes++; } } return $fixedShadowNodes; }
/** * * * @param NodeInterface $node * @param Workspace $targetWorkspace * @return void */ public function afterNodePublishing(NodeInterface $node, Workspace $targetWorkspace) { if (!$this->eventEmittingService->isEnabled()) { return; } $documentNode = NodeEvent::getClosestAggregateNode($node); if ($documentNode === null) { return; } $this->scheduledNodeEventUpdates[$documentNode->getContextPath()] = array('workspaceName' => $node->getContext()->getWorkspaceName(), 'nestedNodeIdentifiersWhichArePublished' => array(), 'targetWorkspace' => $targetWorkspace->getName(), 'documentNode' => $documentNode); $this->scheduledNodeEventUpdates[$documentNode->getContextPath()]['nestedNodeIdentifiersWhichArePublished'][] = $node->getIdentifier(); }
/** * Adds this node to the Node Repository or updates it if it has been added earlier * * @param NodeData $nodeData Other NodeData object to addOrUpdate * @throws \TYPO3\Flow\Persistence\Exception\IllegalObjectTypeException */ protected function addOrUpdate(NodeData $nodeData = null) { $nodeData = $nodeData === null ? $this : $nodeData; // If this NodeData was previously removed and is in live workspace we don't want to add it again to persistence. if ($nodeData->isRemoved() && $this->workspace->getBaseWorkspace() === null) { // Actually it should be removed from the identity map here. if ($this->persistenceManager->isNewObject($nodeData) === false) { $this->nodeDataRepository->remove($nodeData); } } else { if ($this->persistenceManager->isNewObject($nodeData)) { $this->nodeDataRepository->add($nodeData); } else { $this->nodeDataRepository->update($nodeData); } } }
/** * Returns the NodeData instance with the given identifier from the target workspace. * If no NodeData instance is found, NULL is returned. * * @param NodeInterface $node * @param Workspace $targetWorkspace * @return NodeData */ protected function findNodeDataInTargetWorkspace(NodeInterface $node, Workspace $targetWorkspace) { $properties = $node->getContext()->getProperties(); $properties['workspaceName'] = $targetWorkspace->getName(); $targetWorkspaceContext = $this->contextFactory->create($properties); $targetNodeInstance = $targetWorkspaceContext->getNodeByIdentifier($node->getIdentifier()); $targetNode = $targetNodeInstance !== NULL ? $targetNodeInstance->getNodeData() : NULL; return $targetNode; }
/** * Sets the workspace of this node. * * This method is only for internal use by the content repository. Changing * the workspace of a node manually may lead to unexpected behavior. * * @param Workspace $workspace * @return void */ public function setWorkspace(Workspace $workspace) { if (!$this->isNodeDataMatchingContext()) { $this->materializeNodeData(); } if ($this->getWorkspace()->getName() === $workspace->getName()) { return; } $this->nodeData->setWorkspace($workspace); $this->context->getFirstLevelNodeCache()->flush(); $this->emitNodeUpdated($this); }
/** * @test */ public function isPersonalWorkspaceChecksIfTheWorkspaceNameStartsWithUser() { $liveWorkspace = new Workspace('live'); $personalWorkspace = new Workspace('user-admin', $liveWorkspace); $this->assertFalse($liveWorkspace->isPersonalWorkspace()); $this->assertTrue($personalWorkspace->isPersonalWorkspace()); }
/** * @test */ public function publishNodeWithANodeInTheTargetWorkspaceShouldDoNothing() { $liveWorkspace = new Workspace('live'); $personalWorkspace = new Workspace('user-admin', $liveWorkspace); $nodeDataRepository = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Repository\\NodeDataRepository')->disableOriginalConstructor()->getMock(); $this->inject($liveWorkspace, 'nodeDataRepository', $nodeDataRepository); $node = $this->getMock('TYPO3\\TYPO3CR\\Domain\\Model\\NodeInterface'); $node->expects($this->any())->method('getWorkspace')->will($this->returnValue($liveWorkspace)); $nodeDataRepository->expects($this->never())->method('findOneByIdentifier'); $personalWorkspace->publishNode($node, $liveWorkspace); }
/** * Creates a personal workspace for the given user's account if it does not exist already. * * @param User $user The new user to create a workspace for * @param Account $account The user's backend account * @throws IllegalObjectTypeException */ protected function createPersonalWorkspace(User $user, Account $account) { $userWorkspaceName = UserUtility::getPersonalWorkspaceNameForUsername($account->getAccountIdentifier()); $userWorkspace = $this->workspaceRepository->findByIdentifier($userWorkspaceName); if ($userWorkspace === null) { $liveWorkspace = $this->workspaceRepository->findByIdentifier('live'); if (!$liveWorkspace instanceof Workspace) { $liveWorkspace = new Workspace('live'); $liveWorkspace->setTitle('Live'); $this->workspaceRepository->add($liveWorkspace); } $userWorkspace = new Workspace($userWorkspaceName, $liveWorkspace, $user); $userWorkspace->setTitle((string) $user->getName()); $this->workspaceRepository->add($userWorkspace); } }
/** * Publishes the whole workspace * * @param Workspace $workspace * @return void */ public function publishWorkspaceAction(Workspace $workspace) { $liveWorkspace = $this->workspaceRepository->findOneByName('live'); $workspace->publish($liveWorkspace); $this->addFlashMessage('Changes in workspace "%s" have been published', 'Changes published', Message::SEVERITY_OK, array($workspace->getName()), 1412420808); $this->redirect('index'); }
/** * When moving nodes which are inside live workspace to a personal workspace *across levels* (i.e. with different * parent node before and after), the system returned *both* the "new" node from the personal workspace (correct!), * and the "shined-through" version of the node from the "live" workspace (WRONG!). * * For all nodes not being in our base workspace, we need to check whether it is overlaid by a node in our base workspace * with the same identifier. If that's the case, we do not show the node. * * This is a bugfix for #48214. * * @param array $foundNodes * @param Workspace $baseWorkspace * @param array $dimensions * @return array */ protected function filterNodesOverlaidInBaseWorkspace(array $foundNodes, Workspace $baseWorkspace, array $dimensions = NULL) { $identifiersOfNodesNotInBaseWorkspace = array(); /** @var $foundNode NodeData */ foreach ($foundNodes as $i => $foundNode) { if ($foundNode->getWorkspace()->getName() !== $baseWorkspace->getName()) { $identifiersOfNodesNotInBaseWorkspace[$foundNode->getIdentifier()] = $i; } } if (count($identifiersOfNodesNotInBaseWorkspace) === 0) { return $foundNodes; } /** @var \Doctrine\ORM\QueryBuilder $queryBuilder */ $queryBuilder = $this->entityManager->createQueryBuilder(); $queryBuilder->select('n.identifier')->distinct()->from('TYPO3\\TYPO3CR\\Domain\\Model\\NodeData', 'n')->where('n.workspace = :baseWorkspace')->andWhere('n.identifier IN (:identifierList)')->setParameter('baseWorkspace', $baseWorkspace)->setParameter('identifierList', array_keys($identifiersOfNodesNotInBaseWorkspace)); if ($dimensions !== NULL) { $this->addDimensionJoinConstraintsToQueryBuilder($queryBuilder, $dimensions); } $results = $queryBuilder->getQuery()->getArrayResult(); foreach ($results as $result) { $nodeIdentifierOfNodeInBaseWorkspace = $result['identifier']; $indexOfNodeNotInBaseWorkspaceWhichShouldBeRemoved = $identifiersOfNodesNotInBaseWorkspace[$nodeIdentifierOfNodeInBaseWorkspace]; unset($foundNodes[$indexOfNodeNotInBaseWorkspaceWhichShouldBeRemoved]); } return $foundNodes; }
/** * Checks if this instance matches the given workspace and dimensions. * * @param Workspace $workspace * @param array $dimensions * @return boolean */ public function matchesWorkspaceAndDimensions($workspace, array $dimensions = NULL) { if ($this->workspace->getName() !== $workspace->getName()) { return FALSE; } if ($dimensions !== NULL) { $nodeDimensionValues = $this->getDimensionValues(); foreach ($dimensions as $dimensionName => $dimensionValues) { if (!isset($nodeDimensionValues[$dimensionName]) || array_intersect($nodeDimensionValues[$dimensionName], $dimensionValues) === array()) { return FALSE; } } } return TRUE; }
/** * This finds nodes by path and delivers a raw, unfiltered result. * * To get a "usable" set of nodes, filtering by workspaces, dimensions and * removed nodes must be done on the result. * * @param string $path * @param Workspace $workspace * @param array|null $dimensions * @param boolean $onlyShadowNodes * @return array * @throws \InvalidArgumentException */ protected function findRawNodesByPath($path, Workspace $workspace, array $dimensions = null, $onlyShadowNodes = false) { $path = strtolower($path); if ($path === '' || $path !== '/' && ($path[0] !== '/' || substr($path, -1, 1) === '/')) { throw new \InvalidArgumentException('"' . $path . '" is not a valid path: must start but not end with a slash.', 1284985489); } if ($path === '/') { return [$workspace->getRootNodeData()]; } $addedNodes = []; $workspaces = []; while ($workspace !== null) { /** @var $node NodeData */ foreach ($this->addedNodes as $node) { if ($node->getPath() === $path && $node->matchesWorkspaceAndDimensions($workspace, $dimensions) && ($onlyShadowNodes === false || $node->isInternal())) { $addedNodes[] = $node; // removed nodes don't matter here because due to the identity map the right object will be returned from the query and will have "removed" set. } } $workspaces[] = $workspace; $workspace = $workspace->getBaseWorkspace(); } $queryBuilder = $this->createQueryBuilder($workspaces); if ($dimensions !== null) { $this->addDimensionJoinConstraintsToQueryBuilder($queryBuilder, $dimensions); } $this->addPathConstraintToQueryBuilder($queryBuilder, $path); if ($onlyShadowNodes) { $queryBuilder->andWhere('n.movedTo IS NOT NULL AND n.removed = TRUE'); } $query = $queryBuilder->getQuery(); $nodes = $query->getResult(); return array_merge($nodes, $addedNodes); }
/** * Create a new workspace * * This command creates a new workspace. * * @param string $workspace Name of the workspace, for example "christmas-campaign" * @param string $baseWorkspace Name of the base workspace. If none is specified, "live" is assumed. * @param string $title Human friendly title of the workspace, for example "Christmas Campaign" * @param string $description A description explaining the purpose of the new workspace * @param string $owner The identifier of a User to own the workspace * @return void */ public function createCommand($workspace, $baseWorkspace = 'live', $title = null, $description = null, $owner = '') { $workspaceName = $workspace; $workspace = $this->workspaceRepository->findOneByName($workspaceName); if ($workspace instanceof Workspace) { $this->outputLine('Workspace "%s" already exists', [$workspaceName]); $this->quit(1); } $baseWorkspaceName = $baseWorkspace; $baseWorkspace = $this->workspaceRepository->findOneByName($baseWorkspaceName); if (!$baseWorkspace instanceof Workspace) { $this->outputLine('The base workspace "%s" does not exist', [$baseWorkspaceName]); $this->quit(2); } if ($owner === '') { $owningUser = null; } else { $owningUser = $this->userService->getUser($owner); if ($owningUser === null) { $this->outputLine('The user "%s" specified as owner does not exist', [$owner]); $this->quit(3); } } if ($title === null) { $title = $workspaceName; } $workspace = new Workspace($workspaceName, $baseWorkspace, $owningUser); $workspace->setTitle($title); $workspace->setDescription($description); $this->workspaceRepository->add($workspace); if ($owningUser instanceof User) { $this->outputLine('Created a new workspace "%s", based on workspace "%s", owned by "%s".', [$workspaceName, $baseWorkspaceName, $owner]); } else { $this->outputLine('Created a new workspace "%s", based on workspace "%s".', [$workspaceName, $baseWorkspaceName]); } }
/** * Creates a new content context based on the given workspace and the NodeData object. * * @param Workspace $workspace Workspace for the new context * @param array $dimensionValues The dimension values for the new context * @param array $contextProperties Additional pre-defined context properties * @return Context */ protected function createContext(Workspace $workspace, array $dimensionValues, array $contextProperties = array()) { $presetsMatchingDimensionValues = $this->contentDimensionPresetSource->findPresetsByTargetValues($dimensionValues); $dimensions = array_map(function ($preset) { return $preset['values']; }, $presetsMatchingDimensionValues); $contextProperties += array('workspaceName' => $workspace->getName(), 'inaccessibleContentShown' => true, 'invisibleContentShown' => true, 'removedContentShown' => true, 'dimensions' => $dimensions); return $this->contextFactory->create($contextProperties); }
/** * Create a new workspace * * This command creates a new workspace. * * @param string $workspace Name of the workspace, for example "christmas-campaign" * @param string $baseWorkspace Name of the base workspace. If none is specified, "live" is assumed. * @param string $title Human friendly title of the workspace, for example "Christmas Campaign" * @param string $description A description explaining the purpose of the new workspace * @return void */ public function createCommand($workspace, $baseWorkspace = 'live', $title = NULL, $description = NULL) { $workspaceName = $workspace; $workspace = $this->workspaceRepository->findOneByName($workspaceName); if ($workspace instanceof Workspace) { $this->outputLine('Workspace "%s" already exists', array($workspaceName)); $this->quit(1); } $baseWorkspaceName = $baseWorkspace; $baseWorkspace = $this->workspaceRepository->findOneByName($baseWorkspaceName); if (!$baseWorkspace instanceof Workspace) { $this->outputLine('The base workspace "%s" does not exist', array($baseWorkspaceName)); $this->quit(2); } if ($title === NULL) { $title = $workspaceName; } $workspace = new Workspace($workspaceName, $baseWorkspace); $workspace->setTitle($title); $workspace->setDescription($description); $this->workspaceRepository->add($workspace); $this->outputLine('Created a new workspace "%s", based on workspace "%s".', array($workspaceName, $baseWorkspaceName)); }
/** * Checks if this instance matches the given workspace and dimensions. * * @param Workspace $workspace * @param array $dimensions * @return boolean */ public function matchesWorkspaceAndDimensions($workspace, array $dimensions = null) { if ($this->workspace->getName() !== $workspace->getName()) { return false; } if ($dimensions !== null) { $nodeDimensionValues = $this->dimensionValues; foreach ($dimensions as $dimensionName => $dimensionValues) { if (!isset($nodeDimensionValues[$dimensionName]) || array_intersect($nodeDimensionValues[$dimensionName], $dimensionValues) === array()) { return false; } } } return true; }
/** * Publishes the whole workspace * * @param Workspace $workspace * @return void */ public function publishWorkspaceAction(Workspace $workspace) { if (($targetWorkspace = $workspace->getBaseWorkspace()) === null) { $targetWorkspace = $this->workspaceRepository->findOneByName('live'); } $workspace->publish($targetWorkspace); $this->addFlashMessage($this->translator->translateById('workspaces.allChangesInWorkspaceHaveBeenPublished', [htmlspecialchars($workspace->getTitle()), htmlspecialchars($targetWorkspace->getTitle())], null, null, 'Modules', 'TYPO3.Neos')); $this->redirect('index'); }
/** * Checks if the specified workspace is a base workspace of this workspace * and if not, throws an exception * * @param Workspace $targetWorkspace The publishing target workspace * @return void * @throws WorkspaceException if the specified workspace is not a base workspace of this workspace */ protected function verifyPublishingTargetWorkspace(Workspace $targetWorkspace) { $baseWorkspace = $this->baseWorkspace; while ($targetWorkspace !== $baseWorkspace) { if ($baseWorkspace === null) { throw new WorkspaceException(sprintf('The specified workspace "%s" is not a base workspace of "%s".', $targetWorkspace->getName(), $this->getName()), 1289499117); } $baseWorkspace = $baseWorkspace->getBaseWorkspace(); } }
/** * Adds this node to the Node Repository or updates it if it has been added earlier * * @param NodeData $nodeData Other NodeData object to addOrUpdate * @throws IllegalObjectTypeException */ protected function addOrUpdate(NodeData $nodeData = null) { $nodeData = $nodeData === null ? $this : $nodeData; // If this NodeData was previously removed and is in live workspace we don't want to add it again to persistence. if ($nodeData->isRemoved() && $this->workspace->getBaseWorkspace() === null) { // Actually it should be removed from the identity map here. if ($this->persistenceManager->isNewObject($nodeData) === false) { $this->nodeDataRepository->remove($nodeData); } return; } // If the node is marked to be removed but didn't exist in a base workspace yet, we can delete it for real, without creating a shadow node: if ($nodeData->isRemoved() && $this->nodeDataRepository->findOneByIdentifier($nodeData->getIdentifier(), $this->workspace->getBaseWorkspace()) === null) { if ($this->persistenceManager->isNewObject($nodeData) === false) { $this->nodeDataRepository->remove($nodeData); } return; } if ($this->persistenceManager->isNewObject($nodeData)) { $this->nodeDataRepository->add($nodeData); } else { $this->nodeDataRepository->update($nodeData); } }