Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Upgrade to Neos 9 #60

Merged
merged 35 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
43c84b8
Update composer.json
dlubitz May 1, 2023
24ad452
FEATURE: Set version constraints to Neos and Flow 9.0
dlubitz May 1, 2023
cb6ce03
FEATURE: Refactor Redirect Adapter for Neos 9.0
dlubitz May 9, 2023
0d5613c
FEATURE: Add content repository id and nodetype name to signal
dlubitz May 22, 2023
f7e6440
FEATURE: Redirect NeosAdapter for new ContentRepository
dlubitz Jun 13, 2023
bb5d8d8
TASK: Fix tests
dlubitz Jun 20, 2023
e691547
FEATURE: Redirect NeosAdapter for new ContentRepository
dlubitz Jun 20, 2023
74c705c
FEATURE: Redirect NeosAdapter for new ContentRepository
dlubitz Jun 20, 2023
256b494
FEATURE: Redirect NeosAdapter for new ContentRepository
dlubitz Jun 20, 2023
ed6ae0c
FEATURE: Allow route resolving of disabled nodes
dlubitz Jun 27, 2023
8c6a647
FEATURE: Redirect NeosAdapter for new ContentRepository
dlubitz Jun 27, 2023
ce84b69
FEATURE: Redirect NeosAdapter for new ContentRepository
dlubitz Jun 28, 2023
3d3af9f
FEATURE: Redirect NeosAdapter for new ContentRepository
dlubitz Jun 28, 2023
19beb62
Update Classes/CatchUpHook/DocumentUriPathProjectionHook.php
dlubitz Jul 7, 2023
980ce1b
Mini refactoring
bwaidelich Jul 7, 2023
f01d894
Merge branch '90/feature-upgrade-to-neos-9' of https://github.com/neo…
bwaidelich Jul 7, 2023
ea8c997
TASK: Use NodeAddressFactory for NodeAddress creation
dlubitz Aug 24, 2023
4bf4b38
TASK: Remove test configuration
dlubitz Aug 24, 2023
fd781f7
TASK: Use buildService method of CR registry
dlubitz Aug 24, 2023
049017c
Merge remote-tracking branch 'origin/master' into 90/feature-upgrade-…
dlubitz Aug 24, 2023
8ff93f7
Task: Adopt behat tests to latest refactoring in flow and neos behat …
dlubitz Sep 29, 2023
8c6501e
Task: Composer update in pipeline
dlubitz Sep 29, 2023
2c79ffc
TASK: Behat testing in Pipeline
dlubitz Sep 30, 2023
1368a09
TASK: Add workaround for broken behat autoloading
dlubitz Sep 30, 2023
e503178
TASK: Run tests synchronous
dlubitz Sep 30, 2023
539a5bf
TASK: Apply doctrine migrations before running tests
dlubitz Sep 30, 2023
97e441e
TASK: Apply doctrine migrations before running tests
dlubitz Sep 30, 2023
e3cd14e
TASK: Apply doctrine migrations before running tests
dlubitz Sep 30, 2023
b2ef5af
TASK: Check configuration before setup
dlubitz Sep 30, 2023
c0bbf17
TASK: Change flow context to testing
dlubitz Sep 30, 2023
4fed2da
TASK: Change flow context to testing
dlubitz Sep 30, 2023
22aad7b
TASK: Fix styles
dlubitz Sep 30, 2023
e04f362
TASK: Fix styles
dlubitz Sep 30, 2023
11c070b
Merge branch 'main' into 90/feature-upgrade-to-neos-9
dlubitz Oct 6, 2023
892f8a4
TASK: Require proper packages for ci testing
dlubitz Oct 10, 2023
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
92 changes: 77 additions & 15 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,40 @@ on:
jobs:
build:
env:
FLOW_TARGET_VERSION: 6.3
FLOW_CONTEXT: Testing
FLOW_FOLDER: ../flow-base-distribution
NEOS_TARGET_VERSION: '9.0'
NEOS_BASE_FOLDER: neos-base-distribution
PACKAGE_FOLDER: redirect-neosadapter

runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
php-versions: ['7.4']
php-versions: ['8.2']

services:
mariadb:
# see https://mariadb.com/kb/en/mariadb-server-release-dates/
# this should be a current release, e.g. the LTS version
image: mariadb:10.8
env:
MYSQL_USER: neos
MYSQL_PASSWORD: neos
MYSQL_DATABASE: neos_functional_testing
MYSQL_ROOT_PASSWORD: neos
ports:
- "3306:3306"
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

steps:
- uses: actions/checkout@v2
with:
path: ${{ env.PACKAGE_FOLDER }}

- name: Set package branch name
run: echo "PACKAGE_TARGET_VERSION=${GITHUB_BASE_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_ENV
working-directory: .

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -48,21 +69,62 @@ jobs:
path: ~/.composer/cache
key: dependencies-composer-${{ hashFiles('composer.json') }}

- name: Prepare Flow distribution
- name: Checkout development distribution
uses: actions/checkout@v2
with:
repository: neos/neos-development-distribution
ref: ${{ env.NEOS_TARGET_VERSION }}
path: ${{ env.NEOS_BASE_FOLDER }}

- name: Prepare external packages for development distribution
run: |
cd ${NEOS_BASE_FOLDER}
composer require --no-update --no-interaction neos/redirecthandler:"^6.0"
composer require --no-update --no-interaction neos/redirecthandler-databasestorage:"^6.0"

git -C ../${{ env.PACKAGE_FOLDER }} checkout -b build
composer config repositories.package '{ "type": "path", "url": "../${{ env.PACKAGE_FOLDER }}", "options": { "symlink": false } }'
composer require --no-update --no-interaction neos/redirecthandler-neosadapter:"dev-build as dev-${PACKAGE_TARGET_VERSION}"

- name: Composer Install
run: |
git clone https://github.com/neos/flow-base-distribution.git -b ${FLOW_TARGET_VERSION} ${FLOW_FOLDER}
cd ${FLOW_FOLDER}
composer require --no-update --no-interaction neos/redirecthandler-databasestorage:~4.0
composer require --no-update --no-interaction neos/redirecthandler-neosadapter:~4.0
cd ${NEOS_BASE_FOLDER}
composer update --no-interaction --no-progress

- name: Install distribution
- name: Setup Flow configuration
run: |
cd ${FLOW_FOLDER}
composer install --no-interaction --no-progress
rm -rf Packages/Application/Neos.RedirectHandler.NeosAdapter
cp -r ../redirecthandler-neosadapter Packages/Application/Neos.RedirectHandler.NeosAdapter
cd ${NEOS_BASE_FOLDER}
mkdir -p Configuration/Testing
rm -f Configuration/Testing/Settings.yaml
cat <<EOF >> Configuration/Testing/Settings.yaml
Neos:
Flow:
persistence:
backendOptions:
host: '127.0.0.1'
driver: pdo_mysql
user: 'neos'
password: 'neos'
dbname: 'neos_functional_testing'
EOF

- name: Setup database schema
run: |
cd ${NEOS_BASE_FOLDER}
./flow doctrine:migrate --quite

- name: Setup CR
run: |
cd ${NEOS_BASE_FOLDER}
./flow cr:setup

- name: Run Functional tests
run: |
cd ${FLOW_FOLDER}
bin/phpunit --colors -c Build/BuildEssentials/PhpUnit/FunctionalTests.xml Packages/Application/Neos.RedirectHandler.NeosAdapter/Tests/Functional/*
cd ${NEOS_BASE_FOLDER}
CATCHUPTRIGGER_ENABLE_SYNCHRONOUS_OPTION=1 bin/behat -c Packages/Application/Neos.RedirectHandler.NeosAdapter/Tests/Behavior/behat.yml.dist

- name: Show log on failure
if: ${{ failure() }}
run: |
cd ${NEOS_BASE_FOLDER}
cat Data/Logs/System_Testing.log
253 changes: 253 additions & 0 deletions Classes/CatchUpHook/DocumentUriPathProjectionHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
<?php

namespace Neos\RedirectHandler\NeosAdapter\CatchUpHook;

use Neos\ContentRepository\Core\Projection\CatchUpHookInterface;
use Neos\ContentRepository\Core\ContentRepository;
use Neos\ContentRepository\Core\EventStore\EventInterface;
use Neos\EventStore\Model\EventEnvelope;
use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet;
use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved;
use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved;
use Neos\RedirectHandler\NeosAdapter\Service\NodeRedirectService;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\Neos\FrontendRouting\Projection\DocumentUriPathFinder;
use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping;
use Neos\Neos\FrontendRouting\Projection\DocumentNodeInfo;
use Neos\Neos\FrontendRouting\Exception\NodeNotFoundException;
use Neos\Neos\FrontendRouting\NodeAddress;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Neos\FrontendRouting\NodeAddressFactory;

final class DocumentUriPathProjectionHook implements CatchUpHookInterface
{
/**
* Runtime cache to keep DocumentNodeInfos until they get removed.
* @var array<string, array<DocumentNodeInfo>>
*/
private array $documentNodeInfosBeforeRemoval;

public function __construct(
private readonly ContentRepository $contentRepository,
private readonly ContentRepositoryRegistry $contentRepositoryRegistry,
private readonly NodeRedirectService $nodeRedirectService,
) {
}

public function onBeforeCatchUp(): void
{
// Nothing to do here
}

public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $eventEnvelope): void
{
match ($eventInstance::class) {
NodeAggregateWasRemoved::class => $this->onBeforeNodeAggregateWasRemoved($eventInstance),
NodePropertiesWereSet::class => $this->onBeforeNodePropertiesWereSet($eventInstance),
NodeAggregateWasMoved::class => $this->onBeforeNodeAggregateWasMoved($eventInstance),
default => null
};
}

public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $eventEnvelope): void
{
match ($eventInstance::class) {
NodeAggregateWasRemoved::class => $this->onAfterNodeAggregateWasRemoved($eventInstance),
NodePropertiesWereSet::class => $this->onAfterNodePropertiesWereSet($eventInstance),
NodeAggregateWasMoved::class => $this->onAfterNodeAggregateWasMoved($eventInstance),
default => null
};
}

public function onBeforeBatchCompleted(): void
{
// Nothing to do here
}

public function onAfterCatchUp(): void
{
// Nothing to do here
}

private function onBeforeNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): void
{
if (!$this->isLiveContentStream($event->contentStreamId)) {
return;
}

$this->documentNodeInfosBeforeRemoval = [];

foreach ($event->affectedCoveredDimensionSpacePoints as $dimensionSpacePoint) {
$node = $this->findNodeByIdAndDimensionSpacePointHash($event->nodeAggregateId, $dimensionSpacePoint->hash);
if ($node === null) {
// Probably not a document node
continue;
}

$this->nodeRedirectService->appendAffectedNode(
$node,
$this->getNodeAddress($event->contentStreamId, $dimensionSpacePoint, $node->getNodeAggregateId()),
$this->contentRepository->id
);
$this->documentNodeInfosBeforeRemoval[$dimensionSpacePoint->hash][] = $node;

$descendantsOfNode = $this->getState()->getDescendantsOfNode($node);
array_map(
function ($descendantOfNode) use ($event, $dimensionSpacePoint) {
$this->nodeRedirectService->appendAffectedNode(
$descendantOfNode,
$this->getNodeAddress($event->contentStreamId, $dimensionSpacePoint, $descendantOfNode->getNodeAggregateId()),
$this->contentRepository->id
);
$this->documentNodeInfosBeforeRemoval[$dimensionSpacePoint->hash][] = $descendantOfNode;
},
iterator_to_array($descendantsOfNode)
);
}
}

private function onAfterNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): void
{
if (!$this->isLiveContentStream($event->contentStreamId)) {
return;
}

foreach ($event->affectedCoveredDimensionSpacePoints as $dimensionSpacePoint) {
if (!array_key_exists($dimensionSpacePoint->hash, $this->documentNodeInfosBeforeRemoval)) {
continue;
}
$documentNodeInfosBeforeRemoval = $this->documentNodeInfosBeforeRemoval[$dimensionSpacePoint->hash];
dlubitz marked this conversation as resolved.
Show resolved Hide resolved
unset($this->documentNodeInfosBeforeRemoval[$dimensionSpacePoint->hash]);

array_map(
fn (DocumentNodeInfo $node) => $this->nodeRedirectService->createRedirectForRemovedAffectedNode(
$node,
$this->contentRepository->id
),
$documentNodeInfosBeforeRemoval
);
}
}

private function onBeforeNodePropertiesWereSet(NodePropertiesWereSet $event): void
{
$this->handleNodePropertiesWereSet(
$event,
$this->nodeRedirectService->appendAffectedNode(...)
);
}

private function onAfterNodePropertiesWereSet(NodePropertiesWereSet $event): void
{
$this->handleNodePropertiesWereSet(
$event,
$this->nodeRedirectService->createRedirectForAffectedNode(...)
);
}

private function handleNodePropertiesWereSet(NodePropertiesWereSet $event, \Closure $closure): void
{
if (!$this->isLiveContentStream($event->contentStreamId)) {
return;
}

$newPropertyValues = $event->propertyValues->getPlainValues();
if (!isset($newPropertyValues['uriPathSegment'])) {
return;
}

foreach ($event->affectedDimensionSpacePoints as $affectedDimensionSpacePoint) {
$node = $this->findNodeByIdAndDimensionSpacePointHash($event->nodeAggregateId, $affectedDimensionSpacePoint->hash);
if ($node === null) {
// probably not a document node
continue;
}

$closure($node, $this->getNodeAddress($event->contentStreamId, $affectedDimensionSpacePoint, $node->getNodeAggregateId()), $this->contentRepository->id);

$descendantsOfNode = $this->getState()->getDescendantsOfNode($node);
array_map(fn (DocumentNodeInfo $descendantOfNode) => $closure(
$descendantOfNode,
$this->getNodeAddress($event->contentStreamId, $affectedDimensionSpacePoint, $descendantOfNode->getNodeAggregateId()),
$this->contentRepository->id
), iterator_to_array($descendantsOfNode));
}
}

private function onBeforeNodeAggregateWasMoved(NodeAggregateWasMoved $event): void
{
$this->handleNodeWasMoved(
$event,
$this->nodeRedirectService->appendAffectedNode(...)
);
}

private function onAfterNodeAggregateWasMoved(NodeAggregateWasMoved $event): void
{
$this->handleNodeWasMoved(
$event,
$this->nodeRedirectService->createRedirectForAffectedNode(...)
);
}

private function handleNodeWasMoved(NodeAggregateWasMoved $event, \Closure $closure): void
{
if (!$this->isLiveContentStream($event->contentStreamId)) {
return;
}

foreach ($event->nodeMoveMappings as $moveMapping) {
/* @var \Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMapping $moveMapping */
foreach ($moveMapping->newLocations as $newLocation) {
/* @var $newLocation CoverageNodeMoveMapping */
$node = $this->findNodeByIdAndDimensionSpacePointHash($event->nodeAggregateId, $newLocation->coveredDimensionSpacePoint->hash);
if ($node === null) {
// node probably no document node, skip
continue;
}

$closure($node, $this->getNodeAddress($event->contentStreamId, $newLocation->coveredDimensionSpacePoint, $node->getNodeAggregateId()), $this->contentRepository->id);

$descendantsOfNode = $this->getState()->getDescendantsOfNode($node);
array_map(fn (DocumentNodeInfo $descendantOfNode) => $closure(
$descendantOfNode,
$this->getNodeAddress($event->contentStreamId, $newLocation->coveredDimensionSpacePoint, $descendantOfNode->getNodeAggregateId()),
$this->contentRepository->id
), iterator_to_array($descendantsOfNode));
}
}
}

private function getState(): DocumentUriPathFinder
{
return $this->contentRepository->projectionState(DocumentUriPathFinder::class);
}

private function isLiveContentStream(ContentStreamId $contentStreamId): bool
{
return $contentStreamId->equals($this->getState()->getLiveContentStreamId());
}

private function findNodeByIdAndDimensionSpacePointHash(NodeAggregateId $nodeAggregateId, string $dimensionSpacePointHash): ?DocumentNodeInfo
{
try {
return $this->getState()->getByIdAndDimensionSpacePointHash($nodeAggregateId, $dimensionSpacePointHash);
} catch (NodeNotFoundException $_) {
return null;
}
}

protected function getNodeAddress(
ContentStreamId $contentStreamId,
DimensionSpacePoint $dimensionSpacePoint,
NodeAggregateId $nodeAggregateId,
): NodeAddress {
return NodeAddressFactory::create($this->contentRepository)->createFromContentStreamIdAndDimensionSpacePointAndNodeAggregateId(
$contentStreamId,
$dimensionSpacePoint,
$nodeAggregateId
);
}
}
Loading