Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 42 additions & 96 deletions Classes/Command/ErrorPageCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,27 @@

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\StreamWrapper;
use GuzzleHttp\Psr7\Uri;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Service\Context;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Cli\CommandController;
use Neos\Flow\Core\Bootstrap;
use Neos\Neos\Controller\Exception\NodeNotFoundException;
use Neos\Neos\Domain\Model\Domain;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\ContentContextFactory;
use Neos\Neos\Service\LinkingService;
use Neos\Neos\FrontendRouting\Exception\NodeNotFoundException;
use Neos\Neos\FrontendRouting\NodeUriBuilderFactory;
use Neos\Neos\FrontendRouting\Options;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
use Neos\Utility\Files;
use Netlogix\ErrorHandler\Configuration\ErrorHandlerConfiguration;
use Netlogix\ErrorHandler\Service\ControllerContextFactory;
use Netlogix\ErrorHandler\Service\ActionRequestFactory;
use Netlogix\ErrorHandler\Service\DestinationResolver;
use Psr\Http\Message\UriInterface;
use RuntimeException;
use Symfony\Component\Yaml\Yaml;

Expand All @@ -35,47 +40,26 @@
class ErrorPageCommandController extends CommandController
{

/**
* @Flow\Inject
* @var Bootstrap
*/
protected $bootstrap;
#[Flow\Inject]
protected Bootstrap $bootstrap;

/**
* @Flow\Inject
* @var ErrorHandlerConfiguration
*/
protected $configuration;
#[Flow\Inject]
protected ErrorHandlerConfiguration $configuration;

/**
* @Flow\Inject
* @var DestinationResolver
*/
protected $destinationResolver;
#[Flow\Inject]
protected DestinationResolver $destinationResolver;

/**
* @Flow\Inject
* @var SiteRepository
*/
protected $siteRepository;
#[Flow\Inject]
protected SiteRepository $siteRepository;

/**
* @Flow\Inject
* @var ContentContextFactory
*/
protected $contentContextFactory;
#[Flow\Inject]
protected NodeUriBuilderFactory $nodeUriBuilderFactory;

/**
* @Flow\Inject
* @var LinkingService
*/
protected $linkingService;
#[Flow\Inject]
protected ContentRepositoryRegistry $contentRepositoryRegistry;

/**
* @Flow\Inject
* @var ControllerContextFactory
*/
protected $controllerContextFactory;
#[Flow\Inject]
protected ActionRequestFactory $actionRequestFactory;

/**
* Generate Error Pages for configured Sites
Expand Down Expand Up @@ -144,7 +128,7 @@ public function generateCommand(bool $verbose = false)
* @param bool $verbose
* @throws \Exception
*/
public function showConfigurationCommand()
public function showConfigurationCommand(): void
{
$result = [
'Netlogix' => [
Expand All @@ -156,73 +140,35 @@ public function showConfigurationCommand()
$this->output(Yaml::dump($result, 10, 2));
}

/**
* @param Site $site
* @param array $configuration
* @return Uri
* @throws NodeNotFoundException
*/
protected function getSiteUri(Site $site, array $configuration)
protected function getSiteUri(Site $site, array $configuration): UriInterface
{
$domain = $site->getPrimaryDomain();
if (!$domain) {
throw new RuntimeException(sprintf('Site %s has no primary domain', $site->getNodeName()), 1708944032);
}

$context = $this->getContextForSite($site, $domain, $configuration['dimensions']);
$source = $context->getNodeByIdentifier(substr($configuration['source'], 1));
$contentRepository = $this->contentRepositoryRegistry->get($site->getConfiguration()->contentRepositoryId);
$contentGraph = $contentRepository->getContentGraph(WorkspaceName::fromString(WorkspaceName::WORKSPACE_NAME_LIVE));

if (!$source instanceof NodeInterface) {
throw new NodeNotFoundException('Could not get source node in site ' . $site->getNodeName(), 1552492278);
}
$nodeAggregate = $contentGraph->findNodeAggregateById(NodeAggregateId::fromString(substr($configuration['source'], 1)));
$dimensionSpacePoint = DimensionSpacePoint::fromJsonString(json_encode($configuration['dimensions']));

$source = $nodeAggregate->getNodeByCoveredDimensionSpacePoint($dimensionSpacePoint);

if (!$this->isNodeVisible($source)) {
throw new NodeNotFoundException('Node for site ' . $site->getNodeName() . ' is not visible', 1552492532);
throw new NodeNotFoundException('Node ' . $source->aggregateId . ' is not visible', 1552492532);
}

$controllerContext = $this->controllerContextFactory->buildControllerContext(new Uri($domain->__toString()));
$nodeUri = $this->linkingService->createNodeUri($controllerContext, $source, null, null, true);
$siteDetectionResult = SiteDetectionResult::create($site->getNodeName(), $contentRepository->id);
$actionRequest = $this->actionRequestFactory->buildActionRequest($siteDetectionResult);
$nodeUriBuilder = $this->nodeUriBuilderFactory->forActionRequest($actionRequest);

return new Uri($nodeUri);
return $nodeUriBuilder->uriFor(NodeAddress::fromNode($source), Options::createForceAbsolute());
}

/**
* @param NodeInterface $node
* @return bool
*/
protected function isNodeVisible(NodeInterface $node): bool
protected function isNodeVisible(Node $node): bool
{
$currentNode = $node;

do {
if (!$currentNode->isVisible()) {
return false;
}

$currentNode = $currentNode->getParent();
} while ($currentNode !== null);

return true;
}

/**
* @param Site $site
* @param Domain $domain
* @param array $dimensions
* @return Context
*/
protected function getContextForSite(Site $site, Domain $domain, array $dimensions): Context
{
return $this->contentContextFactory->create([
'workspaceName' => 'live',
'targetDimensions' => array_map(function (array $dimensionValues) {
return current($dimensionValues);
}, $dimensions),
'dimensions' => $dimensions,
'currentSite' => $site,
'currentDomain' => $domain,
'invisibleContentShown' => true
]);
return !$node->tags->contain(SubtreeTag::disabled());
}

}
135 changes: 51 additions & 84 deletions Classes/Configuration/ErrorHandlerConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@
namespace Netlogix\ErrorHandler\Configuration;

use Generator;
use Neos\ContentRepository\Domain\Service\ContextFactoryInterface;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\Eel\CompilingEvaluator;
use Neos\Eel\Utility as EelUtility;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\Routing\Dto\RouteParameters;
use Neos\Flow\ObjectManagement\ObjectManagerInterface;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Service\LinkingService;
use Neos\Neos\FrontendRouting\DimensionResolution\DimensionResolverFactoryInterface;
use Neos\Neos\FrontendRouting\DimensionResolution\RequestToDimensionSpacePointContext;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;

use function array_filter;
use function array_values;
use function current;
use function explode;
use function in_array;
use function iterator_to_array;

Expand All @@ -28,40 +31,37 @@
* source: string,
* destination: string,
* dimensionPathSegment?: string,
* pathPrefixes?: string[]
* pathPrefixes?: string[],
* position: int
* }
*/
class ErrorHandlerConfiguration
{
/**
* @Flow\Inject(lazy=false)
* @var CompilingEvaluator
*/
protected $eelEvaluator;

/**
* @Flow\Inject(lazy=false)
* @var ContextFactoryInterface
*/
protected ContextFactoryInterface $contextFactory;

/**
* @Flow\Inject(lazy=false)
* @var LinkingService
*/
protected LinkingService $linkingService;
#[Flow\Inject]
protected CompilingEvaluator $eelEvaluator;

/**
* @Flow\Inject(lazy=false)
* @var NodeBasedConfiguration
*/
#[Flow\Inject]
protected NodeBasedConfiguration $nodeBasedConfiguration;

/**
* @Flow\Inject(lazy=false)
* @var SettingsBasedConfiguration
*/
protected SettingsBasedConfiguration $settingsBasedConfiguration;
#[Flow\Inject]
protected ObjectManagerInterface $objectManager;

private function resolveDimensionSpacePointFromRequest(Site $site, string $requestPath): DimensionSpacePoint
{
$siteConfiguration = $site->getConfiguration();
$dimensionResolverFactory = $this->objectManager->get(
$siteConfiguration->contentDimensionResolverFactoryClassName
);
assert($dimensionResolverFactory instanceof DimensionResolverFactoryInterface);
$dimensionResolver = $dimensionResolverFactory->create($siteConfiguration->contentRepositoryId, $siteConfiguration);
$siteDetectionResult = SiteDetectionResult::create($site->getNodeName(), $siteConfiguration->contentRepositoryId);
$routeParameters = $siteDetectionResult->storeInRouteParameters(RouteParameters::createEmpty());

$dimensionResolverContext = RequestToDimensionSpacePointContext::fromUriPathAndRouteParametersAndResolvedSite($requestPath, $routeParameters, $site);
$dimensionResolverContext = $dimensionResolver->fromRequestToDimensionSpacePoint($dimensionResolverContext);

return $dimensionResolverContext->resolvedDimensionSpacePoint;
}

/**
*
Expand All @@ -77,75 +77,43 @@ class ErrorHandlerConfiguration
*/
public function findConfigurationForSite(
Site $site,
UriInterface $uri,
ServerRequestInterface $request,
int $statusCode
) {
$siteName = $site->getNodeName();
$requestPath = ltrim($uri->getPath() ?? '', '/');
$requestedDimensionPathSegment = current(explode('/', $requestPath, 2));
$siteName = (string)$site->getNodeName();
$requestPath = ltrim($request->getUri()->getPath() ?? '', '/');
$dimensionSpacePoint = $this->resolveDimensionSpacePointFromRequest($site, $requestPath);

$configurationsForSite = $this->getConfiguration();
$configurationsForSite = array_key_exists(
$siteName,
$configurationsForSite
) ? $configurationsForSite[$siteName] : [];

$matchingStatusCodes = array_filter($configurationsForSite,
$configurationsForSite = array_filter($configurationsForSite,
function (array $configuration) use ($statusCode) {
return in_array($statusCode, $configuration['matchingStatusCodes'] ?? [], true);
});

$matchingDimensions = array_filter($matchingStatusCodes,
function (array $configuration) use ($requestedDimensionPathSegment) {
$dimensionPathSegment = $configuration['dimensionPathSegment'] ?? '';
if ($dimensionPathSegment === '' && empty($configuration['dimensions'] ?? [])) {
return true;
}

return $dimensionPathSegment === $requestedDimensionPathSegment;
});

$configurationWithPathPrefixes = array_filter($matchingDimensions,
function (array $configuration) {
return !empty($configuration['pathPrefixes'] ?? []);
});
$configurationsForSite = array_filter($configurationsForSite,
function (array $configuration) use ($dimensionSpacePoint) {
$configuredDimensionSpacePoint = DimensionSpacePoint::fromArray($configuration['dimensions']);

$configurationsWithoutPathPrefixes = array_filter($matchingDimensions,
function (array $configuration) {
return empty($configuration['pathPrefixes'] ?? []);
return $configuredDimensionSpacePoint->equals($dimensionSpacePoint);
});

if (!empty($configurationWithPathPrefixes)) {
$matchingPathPrefixes = array_filter($matchingDimensions,
function (array $configuration) use ($requestPath) {
foreach ($configuration['pathPrefixes'] ?? [] as $pathPrefix) {
if (strpos($requestPath, ltrim($pathPrefix, '/')) === 0) {
return true;
}
$configurationsForSite = array_filter($configurationsForSite,
function (array $configuration) use ($requestPath) {
foreach ($configuration['pathPrefixes'] ?? [] as $pathPrefix) {
if (strpos($requestPath, ltrim($pathPrefix, '/')) === 0) {
return true;
}
}

return false;
});

if (empty($matchingPathPrefixes)) {
return current($configurationsWithoutPathPrefixes);
}

return current($matchingPathPrefixes);
}

return current($configurationsWithoutPathPrefixes) ?: current($matchingStatusCodes) ?: null;
}
return false;
});

/**
* @param string $expression
* @param array $context
* @return mixed
* @throws \Neos\Eel\Exception
*/
protected function evaluateEelExpression(string $expression, array $context)
{
return EelUtility::evaluateEelExpression($expression, $this->eelEvaluator, $context, []);
return current($configurationsForSite) ?: null;
}

/**
Expand All @@ -162,11 +130,10 @@ public function getConfiguration()
private function generateConfiguration(): Generator
{
$nodeBased = $this->nodeBasedConfiguration->getConfiguration();
$settingsBased = $this->settingsBasedConfiguration->getConfiguration();

$sites = array_keys($nodeBased + $settingsBased);
$sites = array_keys($nodeBased);
foreach ($sites as $site) {
yield $site => array_values(array_merge(($nodeBased[$site] ?? []), ($settingsBased[$site] ?? [])));
yield $site => array_values($nodeBased[$site] ?? []);
}
}

Expand Down
Loading