/** * @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); }
/** * {@inheritdoc} */ public function createRedirectsForPublishedNode(NodeInterface $node, Workspace $targetWorkspace) { $nodeType = $node->getNodeType(); if ($targetWorkspace->getName() !== 'live' || !$nodeType->isOfType('Neos.Neos:Document')) { return; } $context = $this->contextFactory->create(['workspaceName' => 'live', 'invisibleContentShown' => true, 'dimensions' => $node->getContext()->getDimensions()]); $targetNode = $context->getNodeByIdentifier($node->getIdentifier()); if ($targetNode === null) { // The page has been added return; } $targetNodeUriPath = $this->buildUriPathForNodeContextPath($targetNode->getContextPath()); if ($targetNodeUriPath === null) { throw new Exception('The target URI path of the node could not be resolved', 1451945358); } $hosts = $this->getHostnames($node->getContext()); // The page has been removed if ($node->isRemoved()) { $this->flushRoutingCacheForNode($targetNode); $statusCode = (int) $this->defaultStatusCode['gone']; $this->redirectStorage->addRedirect($targetNodeUriPath, '', $statusCode, $hosts); return; } // compare the "old" node URI to the new one $nodeUriPath = $this->buildUriPathForNodeContextPath($node->getContextPath()); // use the same regexp than the ContentContextBar Ember View $nodeUriPath = preg_replace('/@[A-Za-z0-9;&,\\-_=]+/', '', $nodeUriPath); if ($nodeUriPath === null || $nodeUriPath === $targetNodeUriPath) { // The page node path has not been changed return; } $this->flushRoutingCacheForNode($targetNode); $statusCode = (int) $this->defaultStatusCode['redirect']; $this->redirectStorage->addRedirect($targetNodeUriPath, $nodeUriPath, $statusCode, $hosts); $q = new FlowQuery([$node]); foreach ($q->children('[instanceof Neos.Neos:Document]') as $childrenNode) { $this->createRedirectsForPublishedNode($childrenNode, $targetWorkspace); } }
/** * @test */ public function publishNodePublishesTheNodeAndItsChildNodeCollectionsIfTheNodeTypeHasChildNodes() { $mockNode = $this->getMockBuilder(NodeInterface::class)->getMock(); $mockChildNode = $this->getMockBuilder(NodeInterface::class)->getMock(); $mockNodeType = $this->getMockBuilder(NodeType::class)->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('Neos.Neos:ContentCollection')->will($this->returnValue(array($mockChildNode))); $mockTargetWorkspace = $this->getMockBuilder(Workspace::class)->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); }
/** * 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); } }
/** * 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; }
/** * 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 * @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 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); } }
/** * * * @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(); }
/** * 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); }
/** * Publishes the whole workspace * * @param Workspace $workspace * @return void */ public function publishWorkspaceAction(Workspace $workspace) { if (($targetWorkspace = $workspace->getBaseWorkspace()) === null) { $targetWorkspace = $this->workspaceRepository->findOneByName('live'); } $this->publishingService->publishNodes($this->publishingService->getUnpublishedNodes($workspace), $targetWorkspace); $this->addFlashMessage($this->translator->translateById('workspaces.allChangesInWorkspaceHaveBeenPublished', [htmlspecialchars($workspace->getTitle()), htmlspecialchars($targetWorkspace->getTitle())], null, null, 'Modules', 'Neos.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; while ($baseWorkspace === null || $targetWorkspace->getName() !== $baseWorkspace->getName()) { 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(); } }
/** * 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()); }