Skip to content

Commit

Permalink
Merge pull request #21 from nezaniel/bugfix/fixUriComparison
Browse files Browse the repository at this point in the history
BUGFIX: Fix node URI path comparison
  • Loading branch information
mficzel authored Jul 6, 2018
2 parents 60fd137 + e042705 commit 9b675ed
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 32 deletions.
68 changes: 36 additions & 32 deletions Classes/Service/NodeRedirectService.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
* source code.
*/

use Neos\ContentRepository\Domain\Factory\NodeFactory;
use Neos\RedirectHandler\Storage\RedirectStorageInterface;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Http\Request;
use Neos\Flow\Log\SystemLoggerInterface;
Expand All @@ -27,7 +27,6 @@
use Neos\Neos\Routing\Exception;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Model\Workspace;
use Neos\ContentRepository\Domain\Repository\NodeDataRepository;

/**
* Service that creates redirects for moved / deleted nodes.
Expand All @@ -38,12 +37,6 @@
*/
class NodeRedirectService implements NodeRedirectServiceInterface
{
/**
* @Flow\Inject
* @var NodeDataRepository
*/
protected $nodeDataRepository;

/**
* @var UriBuilder
*/
Expand Down Expand Up @@ -73,6 +66,12 @@ class NodeRedirectService implements NodeRedirectServiceInterface
*/
protected $contextFactory;

/**
* @Flow\Inject
* @var NodeFactory
*/
protected $nodeFactory;

/**
* @Flow\Inject
* @var SystemLoggerInterface
Expand Down Expand Up @@ -106,67 +105,71 @@ public function createRedirectsForPublishedNode(NodeInterface $node, Workspace $
/**
* Creates a redirect for the node if it is a 'Neos.Neos:Document' node and its URI has changed
*
* @param NodeInterface $node The node that is about to be published
* @param NodeInterface $publishedNode The node that is about to be published
* @param Workspace $targetWorkspace
* @return void
* @throws Exception
* @throws \Neos\Eel\Exception
* @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
*/
protected function executeRedirectsForPublishedNode(NodeInterface $node, Workspace $targetWorkspace)
protected function executeRedirectsForPublishedNode(NodeInterface $publishedNode, Workspace $targetWorkspace)
{
$nodeType = $node->getNodeType();
$nodeType = $publishedNode->getNodeType();
if ($targetWorkspace->getName() !== 'live' || !$nodeType->isOfType('Neos.Neos:Document')) {
return;
}

$context = $this->contextFactory->create([
$liveContext = $this->contextFactory->create([
'workspaceName' => 'live',
'invisibleContentShown' => true,
'dimensions' => $node->getContext()->getDimensions()
'dimensions' => $publishedNode->getContext()->getDimensions()
]);

$targetNode = $context->getNodeByIdentifier($node->getIdentifier());
if ($targetNode === null) {
$liveNode = $liveContext->getNodeByIdentifier($publishedNode->getIdentifier());
if ($liveNode === null) {
// The page has been added
return;
}

$targetNodeUriPath = $this->buildUriPathForNodeContextPath($targetNode->getContextPath());
if ($targetNodeUriPath === null) {
$liveNodeUriPath = $this->buildUriPathForNodeContextPath($liveNode);
if ($liveNodeUriPath === null) {
throw new Exception('The target URI path of the node could not be resolved', 1451945358);
}

$hosts = $this->getHostnames($node->getContext());
$hosts = $this->getHostnames($publishedNode->getContext());

// The page has been removed
if ($node->isRemoved()) {
if ($publishedNode->isRemoved()) {
// By default the redirect handling for removed nodes is activated.
// If it is deactivated in your settings you will be able to handle the redirects on your own.
// For example redirect to dedicated landing pages for deleted campaign NodeTypes
if ($this->enableRemovedNodeRedirect) {
$this->flushRoutingCacheForNode($targetNode);
$this->flushRoutingCacheForNode($liveNode);
$statusCode = (integer)$this->defaultStatusCode['gone'];
$this->redirectStorage->addRedirect($targetNodeUriPath, '', $statusCode, $hosts);
$this->redirectStorage->addRedirect($liveNodeUriPath, '', $statusCode, $hosts);
}

return;
}

// We need to reset the internal node cache to get a node with new path in the same request
$this->nodeFactory->reset();

// compare the "old" node URI to the new one
$nodeUriPath = $this->buildUriPathForNodeContextPath($node->getContextPath());
$publishedNodeUriPath = $this->buildUriPathForNodeContextPath($publishedNode);
// use the same regexp than the ContentContextBar Ember View
$nodeUriPath = preg_replace('/@[A-Za-z0-9;&,\-_=]+/', '', $nodeUriPath);
if ($nodeUriPath === null || $nodeUriPath === $targetNodeUriPath) {
$publishedNodeUriPath = preg_replace('/@[A-Za-z0-9;&,\-_=]+/', '', $publishedNodeUriPath);
if ($publishedNodeUriPath === null || $publishedNodeUriPath === $liveNodeUriPath) {
// The page node path has not been changed
return;
}

$this->flushRoutingCacheForNode($targetNode);
$this->flushRoutingCacheForNode($liveNode);
$statusCode = (integer)$this->defaultStatusCode['redirect'];
$this->redirectStorage->addRedirect($targetNodeUriPath, $nodeUriPath, $statusCode, $hosts);
$this->redirectStorage->addRedirect($liveNodeUriPath, $publishedNodeUriPath, $statusCode, $hosts);

$q = new FlowQuery([$node]);
foreach ($q->children('[instanceof Neos.Neos:Document]') as $childrenNode) {
$this->executeRedirectsForPublishedNode($childrenNode, $targetWorkspace);
foreach ($publishedNode->getChildNodes('Neos.Neos:Document') as $childNode) {
$this->executeRedirectsForPublishedNode($childNode, $targetWorkspace);
}
}

Expand Down Expand Up @@ -208,14 +211,15 @@ protected function flushRoutingCacheForNode(NodeInterface $node)
/**
* Creates a (relative) URI for the given $nodeContextPath removing the "@workspace-name" from the result
*
* @param string $nodeContextPath
* @param NodeInterface $node
* @return string the resulting (relative) URI or NULL if no route could be resolved
* @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
*/
protected function buildUriPathForNodeContextPath($nodeContextPath)
protected function buildUriPathForNodeContextPath(NodeInterface $node)
{
try {
return $this->getUriBuilder()
->uriFor('show', ['node' => $nodeContextPath], 'Frontend\\Node', 'Neos.Neos');
->uriFor('show', ['node' => $node], 'Frontend\\Node', 'Neos.Neos');
} catch (NoMatchingRouteException $exception) {
return null;
}
Expand Down
227 changes: 227 additions & 0 deletions Tests/Functional/Service/NodeRedirectServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
<?php
namespace Neos\RedirectHandler\NeosAdapter\Tests\Functional\Service;

/*
* This file is part of the Neos.Flow package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Model\Workspace;
use Neos\ContentRepository\Domain\Repository\NodeDataRepository;
use Neos\ContentRepository\Domain\Repository\WorkspaceRepository;
use Neos\ContentRepository\Domain\Service\NodeTypeManager;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\ContentContext;
use Neos\Neos\Domain\Service\ContentContextFactory;
use Neos\Neos\Service\PublishingService;
use Neos\RedirectHandler\NeosAdapter\Service\NodeRedirectService;
use Neos\Flow\Tests\FunctionalTestCase;
use Neos\RedirectHandler\Storage\RedirectStorageInterface;

/**
* Functional test cases for the NodeRedirectService
*/
class NodeRedirectServiceTest extends FunctionalTestCase
{
/**
* @var boolean
*/
protected static $testablePersistenceEnabled = true;

/**
* @var NodeRedirectService
*/
protected $nodeRedirectService;

/**
* @var WorkspaceRepository
*/
protected $workspaceRepository;

/**
* @var RedirectStorageInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $mockRedirectStorage;

/**
* @var ContentContextFactory
*/
protected $contentContextFactory;

/**
* @var NodeDataRepository
*/
protected $nodeDataRepository;

/**
* @var NodeTypeManager
*/
protected $nodeTypeManager;

/**
* @var Workspace
*/
protected $liveWorkspace;

/**
* @var Workspace
*/
protected $userWorkspace;

/**
* @var ContentContext
*/
protected $userContext;

/**
* @var NodeInterface
*/
protected $site;

/**
* @var PublishingService
*/
protected $publishingService;

/**
* @var SiteRepository
*/
protected $siteRepository;

/**
* @throws \Neos\Flow\Persistence\Exception\IllegalObjectTypeException
* @throws \Neos\ContentRepository\Exception\NodeExistsException
* @throws \Neos\ContentRepository\Exception\NodeTypeNotFoundException
*/
public function setUp()
{
parent::setUp();
$this->nodeRedirectService = $this->objectManager->get(NodeRedirectService::class);
$this->publishingService = $this->objectManager->get(PublishingService::class);
$this->nodeDataRepository = $this->objectManager->get(NodeDataRepository::class);
$this->siteRepository = $this->objectManager->get(SiteRepository::class);
$this->mockRedirectStorage = $this->getMockBuilder(RedirectStorageInterface::class)->getMock();
$this->inject($this->nodeRedirectService, 'redirectStorage', $this->mockRedirectStorage);
$this->contentContextFactory = $this->objectManager->get(ContentContextFactory::class);
$this->nodeTypeManager = $this->objectManager->get(NodeTypeManager::class);
$this->workspaceRepository = $this->objectManager->get(WorkspaceRepository::class);
$this->liveWorkspace = new Workspace('live');
$this->userWorkspace = new Workspace('user-me', $this->liveWorkspace);
$this->workspaceRepository->add($this->liveWorkspace);
$this->workspaceRepository->add($this->userWorkspace);
$liveContext = $this->contentContextFactory->create([
'workspaceName' => 'live'
]);
$this->userContext = $this->contentContextFactory->create([
'workspaceName' => 'user-me'
]);

$sites = $liveContext->getRootNode()->createNode('sites');
$this->site = $sites->createNode('site', $this->nodeTypeManager->getNodeType('Neos.Neos:Document'), 'site');
$site = new Site('site');
$site->setSiteResourcesPackageKey('My.Package');
$this->siteRepository->add($site);
}

/**
* @return void
*/
public function tearDown()
{
parent::tearDown();
$this->inject($this->contentContextFactory, 'contextInstances', array());
}

/**
* @test
* @throws \Neos\ContentRepository\Exception\NodeExistsException
* @throws \Neos\ContentRepository\Exception\NodeTypeNotFoundException
*/
public function createRedirectsForPublishedNodeCreatesRedirectFromPreviousUriWhenMovingDocumentDown()
{
$documentNodeType = $this->nodeTypeManager->getNodeType('Neos.Neos:Document');

$this->mockRedirectStorage->expects($this->exactly(1))
->method('addRedirect')
->with('/en/document.html', '/en/outer/document.html');

$outerDocument = $this->site->createNode('outer', $documentNodeType);
$outerDocument->setProperty('uriPathSegment', 'outer');
$document = $this->site->createNode('document', $documentNodeType, 'document');
$document->setProperty('uriPathSegment', 'document');

$documentToBeMoved = $this->userContext->adoptNode($document);
$documentToBeMoved->moveInto($outerDocument);

$this->publishingService->publishNode($documentToBeMoved);
}

/**
* @test
* @throws \Neos\ContentRepository\Exception\NodeExistsException
* @throws \Neos\ContentRepository\Exception\NodeTypeNotFoundException
*/
public function createRedirectsForPublishedNodeCreatesRedirectFromPreviousUriWhenMovingDocumentUp()
{
$documentNodeType = $this->nodeTypeManager->getNodeType('Neos.Neos:Document');

$this->mockRedirectStorage->expects($this->exactly(1))
->method('addRedirect')
->with('/en/outer/document.html', '/en/document.html');

$outerDocument = $this->site->createNode('outer', $documentNodeType);
$outerDocument->setProperty('uriPathSegment', 'outer');
$document = $outerDocument->createNode('document', $documentNodeType, 'document');
$document->setProperty('uriPathSegment', 'document');

$documentToBeMoved = $this->userContext->adoptNode($document);
$documentToBeMoved->moveInto($this->site);

$this->publishingService->publishNode($documentToBeMoved);
}

/**
* @test
* @throws \Neos\ContentRepository\Exception\NodeExistsException
* @throws \Neos\ContentRepository\Exception\NodeTypeNotFoundException
*/
public function createRedirectsForPublishedNodeLeavesUpwardRedirectWhenMovingDocumentDownAndUp()
{
$documentNodeType = $this->nodeTypeManager->getNodeType('Neos.Neos:Document');

$this->mockRedirectStorage->expects($this->exactly(2))
->method('addRedirect')
->with($this->logicalOr(
$this->equalTo('/en/document.html'),
$this->equalTo('/en/outer/document.html')
),
$this->logicalOr(
$this->equalTo('/en/outer/document.html'),
$this->equalTo('/en/document.html')
)
);

$outerDocument = $this->site->createNode('outer', $documentNodeType, 'outer');
$outerDocument->setProperty('uriPathSegment', 'outer');
$document = $this->site->createNode('document', $documentNodeType, 'document');
$document->setProperty('uriPathSegment', 'document');

$documentToBeMoved = $this->userContext->adoptNode($document);
$documentToBeMoved->moveInto($this->userContext->getNodeByIdentifier('outer'));
$this->publishingService->publishNode($documentToBeMoved);
$this->nodeDataRepository->persistEntities();

$documentToBeMoved = $this->userContext->adoptNode($outerDocument->getNode('document'));
$documentToBeMoved->moveInto($this->userContext->getNodeByIdentifier('site'));

$this->publishingService->publishNode($documentToBeMoved);
$this->nodeDataRepository->persistEntities();
}
}

0 comments on commit 9b675ed

Please sign in to comment.