/**
  * @test
  */
 public function aWorkspaceCanBeBasedOnAnotherWorkspace()
 {
     $baseWorkspace = new Workspace('BaseWorkspace');
     $workspace = new Workspace('MyWorkspace', $baseWorkspace);
     $this->assertSame('MyWorkspace', $workspace->getName());
     $this->assertSame($baseWorkspace, $workspace->getBaseWorkspace());
 }
 /**
  * @test
  */
 public function getUnpublishedNodesDoesNotReturnInvalidNodes()
 {
     $mockContext = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Service\\Context')->disableOriginalConstructor()->getMock();
     $expectedContextProperties = array('workspaceName' => $this->mockWorkspace->getName(), 'inaccessibleContentShown' => true, 'invisibleContentShown' => true, 'removedContentShown' => true, 'currentSite' => $this->mockSite, 'dimensions' => array());
     $this->mockContextFactory->expects($this->any())->method('create')->with($expectedContextProperties)->will($this->returnValue($mockContext));
     $mockNodeData1 = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Model\\NodeData')->disableOriginalConstructor()->getMock();
     $mockNodeData2 = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Model\\NodeData')->disableOriginalConstructor()->getMock();
     $mockNodeData1->expects($this->any())->method('getDimensionValues')->will($this->returnValue(array()));
     $mockNodeData2->expects($this->any())->method('getDimensionValues')->will($this->returnValue(array()));
     $mockNode1 = $this->getMockBuilder('TYPO3\\TYPO3CR\\Domain\\Model\\NodeInterface')->getMock();
     $mockNode1->expects($this->any())->method('getNodeData')->will($this->returnValue($mockNodeData1));
     $mockNode1->expects($this->any())->method('getPath')->will($this->returnValue('/node1'));
     $this->mockNodeFactory->expects($this->at(0))->method('createFromNodeData')->with($mockNodeData1, $mockContext)->will($this->returnValue($mockNode1));
     $this->mockNodeFactory->expects($this->at(1))->method('createFromNodeData')->with($mockNodeData2, $mockContext)->will($this->returnValue(null));
     $this->mockNodeDataRepository->expects($this->atLeastOnce())->method('findByWorkspace')->with($this->mockWorkspace)->will($this->returnValue(array($mockNodeData1, $mockNodeData2)));
     $actualResult = $this->publishingService->getUnpublishedNodes($this->mockWorkspace);
     $this->assertSame($actualResult, array($mockNode1));
 }
 /**
  * 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;
 }
 /**
  * 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);
 }
 /**
  * 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);
 }
예제 #6
0
 /**
  * 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;
 }
 /**
  *
  *
  * @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();
 }
 /**
  * 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);
 }
 /**
  * Delete a workspace
  *
  * @param Workspace $workspace A workspace to delete
  * @return void
  */
 public function deleteAction(Workspace $workspace)
 {
     if (substr($workspace->getName(), 0, 5) === 'user-') {
         $this->redirect('index');
     }
     $dependentWorkspaces = $this->workspaceRepository->findByBaseWorkspace($workspace);
     if (count($dependentWorkspaces) > 0) {
         $dependentWorkspaceTitles = [];
         /** @var Workspace $dependentWorkspace */
         foreach ($dependentWorkspaces as $dependentWorkspace) {
             $dependentWorkspaceTitles[] = $dependentWorkspace->getTitle();
         }
         $message = $this->translator->translateById('workspaces.workspaceCannotBeDeletedBecauseOfDependencies', [$workspace->getTitle(), implode(', ', $dependentWorkspaceTitles)], null, null, 'Modules', 'TYPO3.Neos');
         $this->addFlashMessage($message, '', Message::SEVERITY_WARNING);
         $this->redirect('index');
     }
     $nodesCount = 0;
     try {
         $nodesCount = $this->publishingService->getUnpublishedNodesCount($workspace);
     } catch (\Exception $exception) {
         $message = $this->translator->translateById('workspaces.notDeletedErrorWhileFetchingUnpublishedNodes', [$workspace->getTitle()], null, null, 'Modules', 'TYPO3.Neos');
         $this->addFlashMessage($message, '', Message::SEVERITY_WARNING);
         $this->redirect('index');
     }
     if ($nodesCount > 0) {
         $message = $this->translator->translateById('workspaces.workspaceCannotBeDeletedBecauseOfUnpublishedNodes', [$workspace->getTitle(), $nodesCount], $nodesCount, null, 'Modules', 'TYPO3.Neos');
         $this->addFlashMessage($message, '', Message::SEVERITY_WARNING);
         $this->redirect('index');
     }
     $this->workspaceRepository->remove($workspace);
     $this->addFlashMessage($message = $this->translator->translateById('workspaces.workspaceHasBeenRemoved', [$workspace->getTitle()], null, null, 'Modules', 'TYPO3.Neos'));
     $this->redirect('index');
 }
 /**
  * Update a workspace
  *
  * @param Workspace $workspace A workspace to update
  * @return void
  */
 public function updateAction(Workspace $workspace)
 {
     if ($workspace->getTitle() === '') {
         $workspace->setTitle($workspace->getName());
     }
     $this->workspaceRepository->update($workspace);
     $this->addFlashMessage($this->translator->translateById('workspaces.workspaceHasBeenUpdated', [$workspace->getTitle()], null, null, 'Modules', 'TYPO3.Neos'));
     $this->redirect('index');
 }
 /**
  * Checks if the current user may read the given workspace according to one the roles of the user's accounts
  *
  * In future versions, this logic may be implemented in Neos in a more generic way (for example, by means of an
  * ACL object), but for now, this method exists in order to at least centralize and encapsulate the required logic.
  *
  * @param Workspace $workspace The workspace
  * @return boolean
  */
 public function currentUserCanReadWorkspace(Workspace $workspace)
 {
     if ($workspace->getName() === 'live') {
         return true;
     }
     if ($workspace->getOwner() === $this->getCurrentUser() || $workspace->getOwner() === null) {
         return true;
     }
     return false;
 }
 /**
  * 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');
 }
예제 #13
0
 /**
  * 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 the current user may publish to the given workspace according to one the roles of the user's accounts
  *
  * In future versions, this logic may be implemented in Neos in a more generic way (for example, by means of an
  * ACL object), but for now, this method exists in order to at least centralize and encapsulate the required logic.
  *
  * @param Workspace $workspace The workspace
  * @return boolean
  */
 public function currentUserCanPublishToWorkspace(Workspace $workspace)
 {
     if ($workspace->getName() === 'live') {
         return $this->securityContext->hasRole('TYPO3.Neos:LivePublisher');
     }
     if ($workspace->getOwner() === $this->getCurrentUser() || $workspace->getOwner() === null) {
         return true;
     }
     return false;
 }
 /**
  * Move this NodeData to the given path and workspace.
  *
  * Basically 4 scenarios have to be covered here, depending on:
  *
  * - Does the NodeData have to be materialized (adapted to the workspace or target dimension)?
  * - Does a shadow node exist on the target path?
  *
  * Because unique key constraints and Doctrine ORM don't support arbitrary removal and update combinations,
  * existing NodeData instances are re-used and the metadata and content is swapped around.
  *
  * @param string $path
  * @param Workspace $workspace
  * @return NodeData If a shadow node was created this is the new NodeData object after the move.
  */
 public function move($path, $workspace)
 {
     $nodeData = $this;
     $originalPath = $this->path;
     if ($originalPath === $path) {
         return $this;
     }
     $targetPathShadowNodeData = $this->getExistingShadowNodeData($path, $workspace, $nodeData->getDimensionValues());
     if ($this->workspace->getName() !== $workspace->getName()) {
         if ($targetPathShadowNodeData === null) {
             // If there is no shadow node at the target path we need to materialize before moving and create a shadow node.
             $nodeData = $this->materializeToWorkspace($workspace);
             $nodeData->setPath($path, false);
             $movedNodeData = $nodeData;
         } else {
             // The existing shadow node on the target path will be used as the moved node. We don't need to materialize into the workspace.
             $movedNodeData = $targetPathShadowNodeData;
             $movedNodeData->setAsShadowOf(null);
             $movedNodeData->setIdentifier($nodeData->getIdentifier());
             $movedNodeData->similarize($nodeData);
             // A new shadow node will be created for the node data that references the recycled, existing shadow node
         }
         $movedNodeData->createShadow($originalPath);
     } else {
         $referencedShadowNode = $this->nodeDataRepository->findOneByMovedTo($nodeData);
         if ($targetPathShadowNodeData === null) {
             if ($referencedShadowNode === null) {
                 // There is no shadow node on the original or target path, so the current node data will be turned to a shadow node and a new node data will be created for the moved node.
                 // We cannot just create a new shadow node, since the order of Doctrine queries would cause unique key conflicts
                 $movedNodeData = new NodeData($path, $nodeData->getWorkspace(), $nodeData->getIdentifier(), $nodeData->getDimensionValues());
                 $movedNodeData->similarize($nodeData);
                 $this->addOrUpdate($movedNodeData);
                 $shadowNodeData = $nodeData;
                 $shadowNodeData->setAsShadowOf($movedNodeData);
             } else {
                 // A shadow node that references this node data already exists, so we just move the current node data
                 $movedNodeData = $nodeData;
                 $movedNodeData->setPath($path, false);
             }
         } else {
             if ($referencedShadowNode === null) {
                 // Turn the target path shadow node into the moved node (and adjust the identifier!)
                 // Do not reset the movedTo property to keep tracing the original move operation - TODO: Does that make sense if the identifier changes?
                 $movedNodeData = $targetPathShadowNodeData;
                 // Since the shadow node at the target path does not belong to the current node, we have to adjust the identifier
                 $movedNodeData->setRemoved(false);
                 $movedNodeData->setIdentifier($nodeData->getIdentifier());
                 $movedNodeData->similarize($nodeData);
                 // Create a shadow node from the current node that shadows the recycled node
                 $shadowNodeData = $nodeData;
                 $shadowNodeData->setAsShadowOf($movedNodeData);
             } else {
                 // If there is already shadow node on the target path, we need to make that shadow node the actual moved node and remove the current node data (which cannot be live).
                 // We cannot remove the shadow node and update the current node data, since the order of Doctrine queries would cause unique key conflicts.
                 $movedNodeData = $targetPathShadowNodeData;
                 $movedNodeData->setAsShadowOf(null);
                 $movedNodeData->similarize($nodeData);
                 $movedNodeData->setPath($path, false);
                 $this->nodeDataRepository->remove($nodeData);
             }
         }
     }
     return $movedNodeData;
 }
 /**
  * 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;
 }
 /**
  * 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();
     }
 }
예제 #18
0
 /**
  * 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;
 }