Skip to content

Commit

Permalink
MERGE: Merge branch '5.2' into 5.3
Browse files Browse the repository at this point in the history
  • Loading branch information
kdambekalns committed Feb 22, 2021
2 parents 9daac80 + fbe1a63 commit 5f4b64b
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 3 deletions.
35 changes: 34 additions & 1 deletion Neos.ContentRepository/Classes/Domain/Model/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -1663,7 +1663,40 @@ protected function createRecursiveCopy(NodeInterface $referenceNode, string $nod
$referenceNodeDimensionsHash = Utility::sortDimensionValueArrayAndReturnDimensionsHash($referenceNodeDimensions);
$thisDimensions = $this->getDimensions();
$thisNodeDimensionsHash = Utility::sortDimensionValueArrayAndReturnDimensionsHash($thisDimensions);
if ($detachedCopy === false && $referenceNodeDimensionsHash !== $thisNodeDimensionsHash && $referenceNode->getContext()->getNodeByIdentifier($this->getIdentifier()) === null) {

// We are only allowed to re-use the node's identifier if the copy-target's context (from $referenceNode)
// does NOT contain this identifier.
// We need to check this also taking removed and invisible nodes into account.
//
// Without changing the context, removedContentShown is typically FALSE, leading to the FOLLOWING BUG:
//
// PREREQUISITES:
// - a language dimension with two values, without fallbacks ("de" and "en")
// - create a page in DE with content nodes "text1" and "text2"
// - translate this page to EN and let it copy all content. "text1" also exists on EN now, and has the same identifier as in DE.
// - publish everything.
//
// REPRODUCING THE BUG: (comment out the removedContentShown line below)
// - select "text1" in "DE" and copy it
// - switch to EN
// - REMOVE the node "text1" in EN
// - PASTE the node from the clipboard AFTER text2 (in EN).
// - (this triggers the code we have here.)
//
// EXPECTED BEHAVIOR
// - the pasted node is shown
//
// ACTUAL BEHAVIOR
// - the pasted node is not shown, but is still in the database.
// - it can happen that the node *is* shown, if it is inserted above the removed node. Still, we have an invariant violation nevertheless.
// - this can also trigger problems when **publishing** the not-rendered-anymore-node (UniqueConstraint errors in the database) - this is
// how we actually found the error.
$contextPropertiesWithAllNodesShown = $referenceNode->getContext()->getProperties();
$contextPropertiesWithAllNodesShown['invisibleContentShown'] = true;
$contextPropertiesWithAllNodesShown['removedContentShown'] = true;
$contextPropertiesWithAllNodesShown['inaccessibleContentShown'] = true;
$referenceNodeContextWithAllNodesShown = $this->contextFactory->create($contextPropertiesWithAllNodesShown);
if ($detachedCopy === false && $referenceNodeDimensionsHash !== $thisNodeDimensionsHash && $referenceNodeContextWithAllNodesShown->getNodeByIdentifier($this->getIdentifier()) === null) {
// If the target dimensions are different than this one, and there is no node shadowing this one in the target dimension yet, we use the same
// node identifier, effectively creating a new node variant.
$identifier = $this->getIdentifier();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
<?php
namespace Neos\ContentRepository\Tests\Functional\Domain;

/*
* This file is part of the Neos.ContentRepository 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\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Tests\FunctionalTestCase;
use Neos\ContentRepository\Domain\Factory\NodeFactory;
use Neos\ContentRepository\Domain\Model\Workspace;
use Neos\ContentRepository\Domain\Repository\ContentDimensionRepository;
use Neos\ContentRepository\Domain\Repository\NodeDataRepository;
use Neos\ContentRepository\Domain\Repository\WorkspaceRepository;
use Neos\ContentRepository\Domain\Service\Context;
use Neos\ContentRepository\Domain\Service\ContextFactoryInterface;
use Neos\ContentRepository\Domain\Service\NodeTypeManager;

/**
* Functional test case which covers https://github.com/neos/neos-development-collection/issues/3265
*/
class CopyNodeAcrossDimensionsBug3265Test extends FunctionalTestCase
{
/**
* @var Context
*/
protected $liveContextDe;

/**
* @var Context
*/
protected $liveContextEn;

/**
* @var Context
*/
protected $userWorkspaceContextDe;

/**
* @var Context
*/
protected $userWorkspaceContextEn;

/**
* @var boolean
*/
protected static $testablePersistenceEnabled = true;

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

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

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

/**
* @var ContextFactoryInterface
*/
protected $contextFactory;

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

/**
* @var ContentDimensionRepository
*/
protected $contentDimensionRepository;

/**
* @return void
*/
public function setUp(): void
{
parent::setUp();
$this->persistAndResetStateCompletely();
}

/**
* @return void
*/
public function tearDown(): void
{
parent::tearDown();
$this->inject($this->contextFactory, 'contextInstances', []);
$configuredDimensions = $this->objectManager->get(ConfigurationManager::class)->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, 'Neos.ContentRepository.contentDimensions');
$this->contentDimensionRepository->setDimensionsConfiguration($configuredDimensions);
}

/**
* @test
*/
public function testForBug3265()
{
// FIXTURE SETUP
// node1 in DE and EN (live)
// node2 in DE and EN (live)
$this->liveContextDe->getRootNode()->createNode('node1', null, 'node1-identifier');
$this->liveContextDe->getRootNode()->createNode('node2', null, 'node2-identifier');
$this->liveContextEn->getRootNode()->createNode('node1', null, 'node1-identifier');
$this->liveContextEn->getRootNode()->createNode('node2', null, 'node2-identifier');
$this->persistAndResetStateCompletely();

// REPRODUCING THE PROBLEM
// 1) we remove the node1 in DE (in user workspace)
$this->userWorkspaceContextDe->getRootNode()->getNode('node1')->remove();
$this->persistAndResetStateCompletely();

// 2) we copy the node1 from EN to DE after node2 (in user workspace)
$enNode1 = $this->userWorkspaceContextEn->getRootNode()->getNode('node1');
$deNode2 = $this->userWorkspaceContextDe->getRootNode()->getNode('node2');
$this->assertNotNull($enNode1, 'enNode1 must not be null');
$this->assertNotNull($deNode2, 'deNode2 must not be null');
$enNode1->copyAfter($deNode2, 'node-1-pasted');
$this->persistAndResetStateCompletely();

// EXPECTATION / ASSERTION
// the copied node must appear underneath the root
$childNodes = $this->userWorkspaceContextDe->getRootNode()->getChildNodes();
$childNodePaths = [];
foreach ($childNodes as $node) {
$childNodePaths[] = $node->getPath();
}
$expected = ['/node2', '/node-1-pasted'];
$this->assertEquals($expected, $childNodePaths, 'We copied node-1-pasted, but it does not appear');
}

protected function persistAndResetStateCompletely()
{
if ($this->nodeDataRepository !== null) {
$this->nodeDataRepository->flushNodeRegistry();
}
/** @var NodeFactory $nodeFactory */
$nodeFactory = $this->objectManager->get(NodeFactory::class);
$nodeFactory->reset();
$this->contextFactory = $this->objectManager->get(ContextFactoryInterface::class);
$this->contextFactory->reset();
$this->persistenceManager->persistAll();
$this->persistenceManager->clearState();
$this->nodeDataRepository = null;
$this->rootNode = null;

$this->nodeDataRepository = new NodeDataRepository();

if ($this->liveWorkspace === null) {
$this->liveWorkspace = new Workspace('live');
$this->objectManager->get(WorkspaceRepository::class);
$this->workspaceRepository = $this->objectManager->get(WorkspaceRepository::class);
$this->workspaceRepository->add($this->liveWorkspace);
$this->workspaceRepository->add(new Workspace('user-admin', $this->liveWorkspace));
$this->persistenceManager->persistAll();
}


$this->liveContextDe = $this->contextFactory->create([
'workspaceName' => 'live',
'dimensions' => [
'language' => ['de']
],
'targetDimensions' => [
'language' => 'de'
]
]);
$this->liveContextEn = $this->contextFactory->create([
'workspaceName' => 'live',
'dimensions' => [
'language' => ['en']
],
'targetDimensions' => [
'language' => 'en'
]
]);
$this->userWorkspaceContextDe = $this->contextFactory->create([
'workspaceName' => 'user-admin',
'dimensions' => [
'language' => ['de']
],
'targetDimensions' => [
'language' => 'de'
]
]);
$this->userWorkspaceContextEn = $this->contextFactory->create([
'workspaceName' => 'user-admin',
'dimensions' => [
'language' => ['en']
],
'targetDimensions' => [
'language' => 'en'
]
]);
$this->nodeTypeManager = $this->objectManager->get(NodeTypeManager::class);
$this->contentDimensionRepository = $this->objectManager->get(ContentDimensionRepository::class);

$this->contentDimensionRepository->setDimensionsConfiguration([
'language' => [
'default' => 'en',
'presets' => [
'enPreset' => [
'values' => ['en']
],
'dePreset' => [
'values' => ['de']
]
]
]
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,11 @@ <h2>{neos:backend.translate(id: 'connectionError', package: 'Neos.Media.Browser'
<script type="text/javascript">
var uploadUrl = "{f:uri.action(action: 'upload', addQueryString: true, additionalParams: {__csrfToken: '{f:security.csrfToken()}'}, absolute: true) -> f:format.raw()}";
var maximumFileUploadSize = {maximumFileUploadSize};

<![CDATA[
if (window.parent !== window && window.parent.NeosMediaBrowserCallbacks && window.parent.NeosMediaBrowserCallbacks.refreshThumbnail) {
window.parent.NeosMediaBrowserCallbacks.refreshThumbnail();
}
]]>
</script>
<f:form action="tagAsset" addQueryString="true" id="tag-asset-form" format="json">
<f:form.hidden name="asset[__identity]" id="tag-asset-form-asset" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ protected function itemLabel(string $pinnedDimensionName = null, NodeInterface $
*/
protected function getNodeInDimensions(array $dimensions, array $targetDimensions)
{
if ($this->currentNode === null) {
return null;
}

$q = new FlowQuery([$this->currentNode]);

return $q->context([
Expand Down
1 change: 0 additions & 1 deletion Neos.Neos/Documentation/Appendixes/ReleaseNotes/520.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ This is a planned feature release and includes the following new feature as well

Update procedure includes updating dependencies and running `./flow doctrine:migrate` as for most updates.

================
What has changed
================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ prototype(Neos.Neos:ImageTag) < prototype(Neos.Fusion:Tag) {

tagName = 'img'
attributes {
alt = ''
src = Neos.Neos:ImageUri {
asset = ${asset}
width = ${width}
Expand Down

0 comments on commit 5f4b64b

Please sign in to comment.