diff --git a/.composer.json b/.composer.json index 67054c5642d..84335a184d5 100644 --- a/.composer.json +++ b/.composer.json @@ -4,6 +4,7 @@ "license": ["GPL-3.0-or-later"], "type": "neos-package-collection", "require": { + "neos/flow-development-collection": "9.0.x-dev" }, "replace": { }, diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 901b635eea4..e31c154b3d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.2'] + php-versions: ['8.2', '8.3'] dependencies: ['highest'] composer-arguments: [''] # to run --ignore-platform-reqs in experimental builds diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 88477f51f32..7a724ed2846 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -122,7 +122,7 @@ private function setupTables(): SetupResult throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - $schema = (new DoctrineDbalContentGraphSchemaBuilder($this->tableNamePrefix))->buildSchema(); + $schema = (new DoctrineDbalContentGraphSchemaBuilder($this->tableNamePrefix))->buildSchema($schemaManager); $schemaDiff = (new Comparator())->compare($schemaManager->createSchema(), $schema); foreach ($schemaDiff->toSaveSql($connection->getDatabasePlatform()) as $statement) { @@ -807,7 +807,7 @@ function (NodeRecord $node) use ($eventEnvelope) { 'nodeanchorpoint' => $nodeAnchorPoint?->value, 'destinationnodeaggregateid' => $reference->targetNodeAggregateId->value, 'properties' => $reference->properties - ? \json_encode($reference->properties, JSON_THROW_ON_ERROR) + ? \json_encode($reference->properties, JSON_THROW_ON_ERROR & JSON_FORCE_OBJECT) : null ]); $position++; diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 7a21d326589..93bbb781c1b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -2,144 +2,107 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter; +use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; +use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; /** * @internal */ class DoctrineDbalContentGraphSchemaBuilder { + private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; + public function __construct( - private readonly string $tableNamePrefix, + private readonly string $tableNamePrefix ) { } - public function buildSchema(): Schema + public function buildSchema(AbstractSchemaManager $schemaManager): Schema { - $schema = new Schema(); - - $this->createNodeTable($schema); - $this->createHierarchyRelationTable($schema); - $this->createReferenceRelationTable($schema); - $this->createRestrictionRelationTable($schema); - - return $schema; + return DbalSchemaFactory::createSchemaWithTables($schemaManager, [ + $this->createNodeTable(), + $this->createHierarchyRelationTable(), + $this->createReferenceRelationTable(), + $this->createRestrictionRelationTable() + ]); } - private function createNodeTable(Schema $schema): void + private function createNodeTable(): Table { + $table = new Table($this->tableNamePrefix . '_node', [ + DbalSchemaFactory::columnForNodeAnchorPoint('relationanchorpoint'), + DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotnull(false), + DbalSchemaFactory::columnForDimensionSpacePoint('origindimensionspacepoint')->setNotnull(false), + DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotnull(false), + DbalSchemaFactory::columnForNodeTypeName('nodetypename'), + (new Column('properties', Type::getType(Types::TEXT)))->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + (new Column('classification', Type::getType(Types::STRING)))->setLength(20)->setNotnull(true)->setCustomSchemaOption('charset', 'binary'), + (new Column('created', Type::getType(Types::DATETIME_IMMUTABLE)))->setDefault('CURRENT_TIMESTAMP')->setNotnull(true), + (new Column('originalcreated', Type::getType(Types::DATETIME_IMMUTABLE)))->setDefault('CURRENT_TIMESTAMP')->setNotnull(true), + (new Column('lastmodified', Type::getType(Types::DATETIME_IMMUTABLE)))->setNotnull(false)->setDefault(null), + (new Column('originallastmodified', Type::getType(Types::DATETIME_IMMUTABLE)))->setNotnull(false)->setDefault(null) + ]); - $table = $schema->createTable($this->tableNamePrefix . '_node'); - $table->addColumn('relationanchorpoint', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table->addColumn('nodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(false); - $table->addColumn('origindimensionspacepoint', Types::TEXT) - ->setNotnull(false); - $table->addColumn('origindimensionspacepointhash', Types::STRING) - ->setLength(255) - ->setNotnull(false); - $table->addColumn('nodetypename', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table->addColumn('properties', Types::TEXT) // TODO longtext? - ->setNotnull(true); - $table->addColumn('classification', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table->addColumn('created', Types::DATETIME_IMMUTABLE) - ->setDefault('CURRENT_TIMESTAMP') - ->setNotnull(true); - $table->addColumn('originalcreated', Types::DATETIME_IMMUTABLE) - ->setDefault('CURRENT_TIMESTAMP') - ->setNotnull(true); - $table->addColumn('lastmodified', Types::DATETIME_IMMUTABLE) - ->setNotnull(false) - ->setDefault(null); - $table->addColumn('originallastmodified', Types::DATETIME_IMMUTABLE) - ->setNotnull(false) - ->setDefault(null); - $table + return $table ->setPrimaryKey(['relationanchorpoint']) ->addIndex(['nodeaggregateid']) ->addIndex(['nodetypename']); } - private function createHierarchyRelationTable(Schema $schema): void + private function createHierarchyRelationTable(): Table { - $table = $schema->createTable($this->tableNamePrefix . '_hierarchyrelation'); - $table->addColumn('name', Types::STRING) - ->setLength(255) - ->setNotnull(false); - $table->addColumn('position', Types::INTEGER) - ->setNotnull(true); - $table->addColumn('contentstreamid', Types::STRING) - ->setLength(40) - ->setNotnull(true); - $table->addColumn('dimensionspacepoint', Types::TEXT) - ->setNotnull(true); - $table->addColumn('dimensionspacepointhash', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table->addColumn('parentnodeanchor', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table->addColumn('childnodeanchor', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table + $table = new Table($this->tableNamePrefix . '_hierarchyrelation', [ + (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), + DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), + DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotnull(true), + DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash')->setNotnull(true), + DbalSchemaFactory::columnForNodeAnchorPoint('parentnodeanchor'), + DbalSchemaFactory::columnForNodeAnchorPoint('childnodeanchor') + ]); + + return $table ->addIndex(['childnodeanchor']) ->addIndex(['contentstreamid']) ->addIndex(['parentnodeanchor']) + ->addIndex(['contentstreamid', 'childnodeanchor', 'dimensionspacepointhash']) ->addIndex(['contentstreamid', 'dimensionspacepointhash']); } - private function createReferenceRelationTable(Schema $schema): void + private function createReferenceRelationTable(): Table { - $table = $schema->createTable($this->tableNamePrefix . '_referencerelation'); - $table->addColumn('name', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table->addColumn('position', Types::INTEGER) - ->setNotnull(true); - $table->addColumn('nodeanchorpoint', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table->addColumn('properties', Types::TEXT) - ->setNotnull(false); - $table->addColumn('destinationnodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(true); + $table = new Table($this->tableNamePrefix . '_referencerelation', [ + (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), + (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), + DbalSchemaFactory::columnForNodeAnchorPoint('nodeanchorpoint'), + (new Column('properties', Type::getType(Types::TEXT)))->setNotnull(false)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + DbalSchemaFactory::columnForNodeAggregateId('destinationnodeaggregateid')->setNotnull(true) + ]); - $table + return $table ->setPrimaryKey(['name', 'position', 'nodeanchorpoint']); } - private function createRestrictionRelationTable(Schema $schema): void + private function createRestrictionRelationTable(): Table { - $table = $schema->createTable($this->tableNamePrefix . '_restrictionrelation'); - $table->addColumn('contentstreamid', Types::STRING) - ->setLength(40) - ->setNotnull(true); - $table->addColumn('dimensionspacepointhash', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $table->addColumn('originnodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(true); - $table->addColumn('affectednodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(true); + $table = new Table($this->tableNamePrefix . '_restrictionrelation', [ + DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), + DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash')->setNotnull(true), + DbalSchemaFactory::columnForNodeAggregateId('originnodeaggregateid')->setNotnull(false), + DbalSchemaFactory::columnForNodeAggregateId('affectednodeaggregateid')->setNotnull(false), + ]); - $table - ->setPrimaryKey([ - 'contentstreamid', - 'dimensionspacepointhash', - 'originnodeaggregateid', - 'affectednodeaggregateid' - ]); + return $table->setPrimaryKey([ + 'contentstreamid', + 'dimensionspacepointhash', + 'originnodeaggregateid', + 'affectednodeaggregateid' + ]); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 3e39613f33b..3ede770bd5d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -15,36 +15,48 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\Exception as DriverException; +use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Factory\ContentRepositoryId; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphWithRuntimeCaches\ContentSubgraphWithRuntimeCaches; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; -use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregates; -use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; -use Neos\ContentRepository\Core\SharedModel\Node\NodeName; -use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; -use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; -use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphWithRuntimeCaches\ContentSubgraphWithRuntimeCaches; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregates; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\SharedModel\Node\NodeName; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * The Doctrine DBAL adapter content graph * * To be used as a read-only source of nodes * + * ## Conventions for SQL queries + * + * - n -> node + * - h -> hierarchy edge + * + * - if more than one node (parent-child) + * - pn -> parent node + * - cn -> child node + * - h -> the hierarchy edge connecting parent and child + * - ph -> the hierarchy edge incoming to the parent (sometimes relevant) + * * @internal the parent interface {@see ContentGraphInterface} is API */ final class ContentGraph implements ContentGraphInterface @@ -124,289 +136,159 @@ public function findRootNodeAggregates( ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter, ): NodeAggregates { - $connection = $this->client->getConnection(); - - $query = 'SELECT n.*, h.contentstreamid, h.name, h.dimensionspacepoint AS covereddimensionspacepoint - FROM ' . $this->tableNamePrefix . '_node n - JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h - ON h.childnodeanchor = n.relationanchorpoint - WHERE h.contentstreamid = :contentStreamId - AND h.parentnodeanchor = :rootEdgeParentAnchorId '; - - $parameters = [ - 'contentStreamId' => $contentStreamId->value, - 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, - ]; + $queryBuilder = $this->createQueryBuilder() + ->select('n.*, h.contentstreamid, h.name, h.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->tableNamePrefix . '_node', 'n') + ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('h.contentstreamid = :contentStreamId') + ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') + ->setParameters([ + 'contentStreamId' => $contentStreamId->value, + 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, + ]); if ($filter->nodeTypeName !== null) { - $query .= ' AND n.nodetypename = :nodeTypeName'; - $parameters['nodeTypeName'] = $filter->nodeTypeName->value; + $queryBuilder + ->andWhere('n.nodetypename = :nodeTypeName') + ->setParameter('nodeTypeName', $filter->nodeTypeName->value); } - - - $nodeRows = $connection->executeQuery($query, $parameters)->fetchAllAssociative(); - - - /** @var \Traversable $nodeAggregates The factory will return a NodeAggregate since the array is not empty */ - $nodeAggregates = $this->nodeFactory->mapNodeRowsToNodeAggregates( - $nodeRows, - VisibilityConstraints::withoutRestrictions() - ); - - return NodeAggregates::fromArray(iterator_to_array($nodeAggregates)); + return NodeAggregates::fromArray(iterator_to_array($this->mapQueryBuilderToNodeAggregates($queryBuilder))); } public function findNodeAggregatesByType( ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): iterable { - $connection = $this->client->getConnection(); - - $query = 'SELECT n.*, h.contentstreamid, h.name, h.dimensionspacepoint AS covereddimensionspacepoint - FROM ' . $this->tableNamePrefix . '_node n - JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h - ON h.childnodeanchor = n.relationanchorpoint - WHERE h.contentstreamid = :contentStreamId - AND n.nodetypename = :nodeTypeName'; - - $parameters = [ - 'contentStreamId' => $contentStreamId->value, - 'nodeTypeName' => $nodeTypeName->value, - ]; - - $resultStatement = $connection->executeQuery($query, $parameters)->fetchAllAssociative(); - - return $this->nodeFactory->mapNodeRowsToNodeAggregates( - $resultStatement, - VisibilityConstraints::withoutRestrictions() - ); + $queryBuilder = $this->createQueryBuilder() + ->select('n.*, h.contentstreamid, h.name, h.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->tableNamePrefix . '_node', 'n') + ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->where('h.contentstreamid = :contentStreamId') + ->andWhere('n.nodetypename = :nodeTypeName') + ->setParameters([ + 'contentStreamId' => $contentStreamId->value, + 'nodeTypeName' => $nodeTypeName->value, + ]); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - /** - * @throws DBALException - * @throws \Exception - */ public function findNodeAggregateById( ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): ?NodeAggregate { - $connection = $this->client->getConnection(); - - $query = 'SELECT n.*, - h.name, h.contentstreamid, h.dimensionspacepoint AS covereddimensionspacepoint, - r.dimensionspacepointhash AS disableddimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - JOIN ' . $this->tableNamePrefix . '_node n ON n.relationanchorpoint = h.childnodeanchor - LEFT JOIN ' . $this->tableNamePrefix . '_restrictionrelation r - ON r.originnodeaggregateid = n.nodeaggregateid - AND r.contentstreamid = h.contentstreamid - AND r.affectednodeaggregateid = n.nodeaggregateid - AND r.dimensionspacepointhash = h.dimensionspacepointhash - WHERE n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamid = :contentStreamId'; - $parameters = [ - 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value - ]; - - $nodeRows = $connection->executeQuery($query, $parameters)->fetchAllAssociative(); + $queryBuilder = $this->createQueryBuilder() + ->select('n.*, h.name, h.contentstreamid, h.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') + ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') + ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.childnodeanchor') + ->leftJoin('h', $this->tableNamePrefix . '_restrictionrelation', 'r', 'r.originnodeaggregateid = n.nodeaggregateid AND r.contentstreamid = :contentStreamId AND r.affectednodeaggregateid = n.nodeaggregateid AND r.dimensionspacepointhash = h.dimensionspacepointhash') + ->where('n.nodeaggregateid = :nodeAggregateId') + ->andWhere('h.contentstreamid = :contentStreamId') + ->setParameters([ + 'nodeAggregateId' => $nodeAggregateId->value, + 'contentStreamId' => $contentStreamId->value + ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( - $nodeRows, + $this->fetchRows($queryBuilder), VisibilityConstraints::withoutRestrictions() ); } /** * @return iterable - * @throws DBALException - * @throws \Exception */ public function findParentNodeAggregates( ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId ): iterable { - $connection = $this->client->getConnection(); - - $query = 'SELECT p.*, - ph.name, ph.contentstreamid, ph.dimensionspacepoint AS covereddimensionspacepoint, - r.dimensionspacepointhash AS disableddimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_node p - JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph - ON ph.childnodeanchor = p.relationanchorpoint - JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ch - ON ch.parentnodeanchor = p.relationanchorpoint - JOIN ' . $this->tableNamePrefix . '_node c ON ch.childnodeanchor = c.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_restrictionrelation r - ON r.originnodeaggregateid = p.nodeaggregateid - AND r.contentstreamid = ph.contentstreamid - AND r.affectednodeaggregateid = p.nodeaggregateid - AND r.dimensionspacepointhash = ph.dimensionspacepointhash - WHERE c.nodeaggregateid = :nodeAggregateId - AND ph.contentstreamid = :contentStreamId - AND ch.contentstreamid = :contentStreamId'; - $parameters = [ - 'nodeAggregateId' => $childNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value - ]; - - $nodeRows = $connection->executeQuery($query, $parameters)->fetchAllAssociative(); - - return $this->nodeFactory->mapNodeRowsToNodeAggregates( - $nodeRows, - VisibilityConstraints::withoutRestrictions() - ); + $queryBuilder = $this->createQueryBuilder() + ->select('pn.*, ph.name, ph.contentstreamid, ph.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') + ->from($this->tableNamePrefix . '_node', 'pn') + ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->leftJoin('ph', $this->tableNamePrefix . '_restrictionrelation', 'r', 'r.originnodeaggregateid = pn.nodeaggregateid AND r.contentstreamid = :contentStreamId AND r.affectednodeaggregateid = pn.nodeaggregateid AND r.dimensionspacepointhash = ph.dimensionspacepointhash') + ->where('cn.nodeaggregateid = :nodeAggregateId') + ->andWhere('ph.contentstreamid = :contentStreamId') + ->andWhere('ch.contentstreamid = :contentStreamId') + ->setParameters([ + 'nodeAggregateId' => $childNodeAggregateId->value, + 'contentStreamId' => $contentStreamId->value + ]); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - /** - * @throws DBALException - * @throws \Exception - */ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): ?NodeAggregate { - $connection = $this->client->getConnection(); - - $query = 'SELECT n.*, - h.name, h.contentstreamid, h.dimensionspacepoint AS covereddimensionspacepoint, - r.dimensionspacepointhash AS disableddimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_node n - JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h - ON h.childnodeanchor = n.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_restrictionrelation r - ON r.originnodeaggregateid = n.nodeaggregateid - AND r.contentstreamid = h.contentstreamid - AND r.affectednodeaggregateid = n.nodeaggregateid - AND r.dimensionspacepointhash = h.dimensionspacepointhash - WHERE n.nodeaggregateid = ( - SELECT p.nodeaggregateid FROM ' . $this->tableNamePrefix . '_node p - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ch - ON ch.parentnodeanchor = p.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_node c - ON ch.childnodeanchor = c.relationanchorpoint - WHERE ch.contentstreamid = :contentStreamId - AND ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash - AND c.nodeaggregateid = :childNodeAggregateId - AND c.origindimensionspacepointhash = :childOriginDimensionSpacePointHash - ) - AND h.contentstreamid = :contentStreamId'; - - $parameters = [ - 'contentStreamId' => $contentStreamId->value, - 'childNodeAggregateId' => $childNodeAggregateId->value, - 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash, - ]; - - $nodeRows = $connection->executeQuery($query, $parameters)->fetchAllAssociative(); + $subQueryBuilder = $this->createQueryBuilder() + ->select('pn.nodeaggregateid') + ->from($this->tableNamePrefix . '_node', 'pn') + ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->where('ch.contentstreamid = :contentStreamId') + ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') + ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') + ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); + + $queryBuilder = $this->createQueryBuilder() + ->select('n.*, h.name, h.contentstreamid, h.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') + ->from($this->tableNamePrefix . '_node', 'n') + ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->leftJoin('h', $this->tableNamePrefix . '_restrictionrelation', 'r', 'r.originnodeaggregateid = n.nodeaggregateid AND r.contentstreamid = :contentStreamId AND r.affectednodeaggregateid = n.nodeaggregateid AND r.dimensionspacepointhash = h.dimensionspacepointhash') + ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') + ->andWhere('h.contentstreamid = :contentStreamId') + ->setParameters([ + 'contentStreamId' => $contentStreamId->value, + 'childNodeAggregateId' => $childNodeAggregateId->value, + 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash, + ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( - $nodeRows, + $this->fetchRows($queryBuilder), VisibilityConstraints::withoutRestrictions() ); } /** * @return iterable - * @throws DBALException|\Exception */ public function findChildNodeAggregates( ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable { - $connection = $this->client->getConnection(); - - $query = $this->createChildNodeAggregateQuery(); - - $parameters = [ - 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value - ]; - - $nodeRows = $connection->executeQuery($query, $parameters)->fetchAllAssociative(); - - return $this->nodeFactory->mapNodeRowsToNodeAggregates( - $nodeRows, - VisibilityConstraints::withoutRestrictions() - ); + $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } /** * @return iterable - * @throws DBALException|NodeTypeNotFoundException */ public function findChildNodeAggregatesByName( ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId, NodeName $name ): iterable { - $connection = $this->client->getConnection(); - - $query = $this->createChildNodeAggregateQuery() . ' - AND ch.name = :relationName'; - - $parameters = [ - 'contentStreamId' => $contentStreamId->value, - 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'relationName' => $name->value - ]; - - $nodeRows = $connection->executeQuery($query, $parameters)->fetchAllAssociative(); - - return $this->nodeFactory->mapNodeRowsToNodeAggregates( - $nodeRows, - VisibilityConstraints::withoutRestrictions() - ); + $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId) + ->andWhere('ch.name = :relationName') + ->setParameter('relationName', $name->value); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } /** * @return iterable - * @throws DBALException|NodeTypeNotFoundException */ public function findTetheredChildNodeAggregates( ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable { - $connection = $this->client->getConnection(); - - $query = $this->createChildNodeAggregateQuery() . ' - AND c.classification = :tetheredClassification'; - - $parameters = [ - 'contentStreamId' => $contentStreamId->value, - 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'tetheredClassification' => NodeAggregateClassification::CLASSIFICATION_TETHERED->value - ]; - - $nodeRows = $connection->executeQuery($query, $parameters)->fetchAllAssociative(); - - return $this->nodeFactory->mapNodeRowsToNodeAggregates( - $nodeRows, - VisibilityConstraints::withoutRestrictions() - ); - } - - private function createChildNodeAggregateQuery(): string - { - return 'SELECT c.*, - ch.name, ch.contentstreamid, ch.dimensionspacepoint AS covereddimensionspacepoint, - r.dimensionspacepointhash AS disableddimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_node p - JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph - ON ph.childnodeanchor = p.relationanchorpoint - JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ch - ON ch.parentnodeanchor = p.relationanchorpoint - JOIN ' . $this->tableNamePrefix . '_node c - ON ch.childnodeanchor = c.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_restrictionrelation r - ON r.originnodeaggregateid = p.nodeaggregateid - AND r.contentstreamid = ph.contentstreamid - AND r.affectednodeaggregateid = p.nodeaggregateid - AND r.dimensionspacepointhash = ph.dimensionspacepointhash - WHERE p.nodeaggregateid = :parentNodeAggregateId - AND ph.contentstreamid = :contentStreamId - AND ch.contentstreamid = :contentStreamId'; + $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId) + ->andWhere('cn.classification = :tetheredClassification') + ->setParameter('tetheredClassification', NodeAggregateClassification::CLASSIFICATION_TETHERED->value); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } /** @@ -416,7 +298,6 @@ private function createChildNodeAggregateQuery(): string * @param OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint * @param DimensionSpacePointSet $dimensionSpacePointsToCheck * @return DimensionSpacePointSet - * @throws DBALException */ public function getDimensionSpacePointsOccupiedByChildNodeName( ContentStreamId $contentStreamId, @@ -425,62 +306,55 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet { - $connection = $this->client->getConnection(); - - $query = 'SELECT h.dimensionspacepoint, h.dimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_node n - ON h.parentnodeanchor = n.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph - ON ph.childnodeanchor = n.relationanchorpoint - WHERE n.nodeaggregateid = :parentNodeAggregateId - AND n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash - AND ph.contentstreamid = :contentStreamId - AND h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes) - AND h.name = :nodeName'; - $parameters = [ - 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, - 'contentStreamId' => $contentStreamId->value, - 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), - 'nodeName' => $nodeName->value - ]; - $types = [ - 'dimensionSpacePointHashes' => Connection::PARAM_STR_ARRAY - ]; + $queryBuilder = $this->createQueryBuilder() + ->select('h.dimensionspacepoint, h.dimensionspacepointhash') + ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') + ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->where('n.nodeaggregateid = :parentNodeAggregateId') + ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') + ->andWhere('ph.contentstreamid = :contentStreamId') + ->andWhere('h.contentstreamid = :contentStreamId') + ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') + ->andWhere('h.name = :nodeName') + ->setParameters([ + 'parentNodeAggregateId' => $parentNodeAggregateId->value, + 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, + 'contentStreamId' => $contentStreamId->value, + 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), + 'nodeName' => $nodeName->value + ], [ + 'dimensionSpacePointHashes' => Connection::PARAM_STR_ARRAY, + ]); $dimensionSpacePoints = []; - foreach ( - $connection->executeQuery($query, $parameters, $types) - ->fetchAllAssociative() as $hierarchyRelationData - ) { - $dimensionSpacePoints[$hierarchyRelationData['dimensionspacepointhash']] - = DimensionSpacePoint::fromJsonString($hierarchyRelationData['dimensionspacepoint']); + foreach ($this->fetchRows($queryBuilder) as $hierarchyRelationData) { + $dimensionSpacePoints[$hierarchyRelationData['dimensionspacepointhash']] = DimensionSpacePoint::fromJsonString($hierarchyRelationData['dimensionspacepoint']); } - return new DimensionSpacePointSet($dimensionSpacePoints); } public function countNodes(): int { - $connection = $this->client->getConnection(); - $query = 'SELECT COUNT(*) FROM ' . $this->tableNamePrefix . '_node'; - - $row = $connection->executeQuery($query)->fetchAssociative(); - - return $row ? (int)$row['COUNT(*)'] : 0; + $queryBuilder = $this->createQueryBuilder() + ->select('COUNT(*)') + ->from($this->tableNamePrefix . '_node'); + $result = $queryBuilder->execute(); + if (!$result instanceof Result) { + throw new \RuntimeException(sprintf('Failed to count nodes. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701444550); + } + try { + return (int)$result->fetchOne(); + } catch (DriverException | DBALException $e) { + throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444590, $e); + } } public function findUsedNodeTypeNames(): iterable { - $connection = $this->client->getConnection(); - - $rows = $connection->executeQuery('SELECT DISTINCT nodetypename FROM ' . $this->tableNamePrefix . '_node') - ->fetchAllAssociative(); - - return array_map(function (array $row) { - return NodeTypeName::fromString($row['nodetypename']); - }, $rows); + $rows = $this->fetchRows($this->createQueryBuilder() + ->select('DISTINCT nodetypename') + ->from($this->tableNamePrefix . '_node')); + return array_map(static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), $rows); } /** @@ -491,4 +365,56 @@ public function getSubgraphs(): array { return $this->subgraphs; } + + private function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder + { + return $this->createQueryBuilder() + ->select('cn.*, ch.name, ch.contentstreamid, ch.dimensionspacepoint AS covereddimensionspacepoint, r.dimensionspacepointhash AS disableddimensionspacepointhash') + ->from($this->tableNamePrefix . '_node', 'pn') + ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->leftJoin('pn', $this->tableNamePrefix . '_restrictionrelation', 'r', 'r.originnodeaggregateid = pn.nodeaggregateid AND r.contentstreamid = :contentStreamId AND r.affectednodeaggregateid = pn.nodeaggregateid AND r.dimensionspacepointhash = ph.dimensionspacepointhash') + ->where('pn.nodeaggregateid = :parentNodeAggregateId') + ->andWhere('ph.contentstreamid = :contentStreamId') + ->andWhere('ch.contentstreamid = :contentStreamId') + ->orderBy('ch.position') + ->setParameters([ + 'parentNodeAggregateId' => $parentNodeAggregateId->value, + 'contentStreamId' => $contentStreamId->value, + ]); + } + + private function createQueryBuilder(): QueryBuilder + { + return $this->client->getConnection()->createQueryBuilder(); + } + + /** + * @param QueryBuilder $queryBuilder + * @return iterable + */ + private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder): iterable + { + return $this->nodeFactory->mapNodeRowsToNodeAggregates( + $this->fetchRows($queryBuilder), + VisibilityConstraints::withoutRestrictions() + ); + } + + /** + * @return array> + */ + private function fetchRows(QueryBuilder $queryBuilder): array + { + $result = $queryBuilder->execute(); + if (!$result instanceof Result) { + throw new \RuntimeException(sprintf('Failed to execute query. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701443535); + } + try { + return $result->fetchAllAssociative(); + } catch (DriverException | DBALException $e) { + throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); + } + } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 4100d614945..aa77993c975 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -175,7 +175,7 @@ public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash); - $this->addRestrictionRelationConstraints($queryBuilder); + $this->addRestrictionRelationConstraints($queryBuilder, 'n', 'h', ':contentStreamId', ':dimensionSpacePointHash'); return $this->fetchNode($queryBuilder); } @@ -190,7 +190,7 @@ public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) ->andWhere('n.classification = :nodeAggregateClassification') ->setParameter('nodeAggregateClassification', NodeAggregateClassification::CLASSIFICATION_ROOT->value); - $this->addRestrictionRelationConstraints($queryBuilder); + $this->addRestrictionRelationConstraints($queryBuilder, 'n', 'h', ':contentStreamId', ':dimensionSpacePointHash'); return $this->fetchNode($queryBuilder); } @@ -207,12 +207,14 @@ public function findParentNode(NodeAggregateId $childNodeAggregateId): ?Node ->andWhere('ch.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); - $this->addRestrictionRelationConstraints($queryBuilder, 'cn', 'ch'); + $this->addRestrictionRelationConstraints($queryBuilder, 'cn', 'ch', ':contentStreamId', ':dimensionSpacePointHash'); return $this->fetchNode($queryBuilder); } - public function findNodeByPath(NodePath $path, NodeAggregateId $startingNodeAggregateId): ?Node + public function findNodeByPath(NodePath|NodeName $path, NodeAggregateId $startingNodeAggregateId): ?Node { + $path = $path instanceof NodeName ? NodePath::fromNodeNames($path) : $path; + $startingNode = $this->findNodeById($startingNodeAggregateId); return $startingNode @@ -229,7 +231,12 @@ public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node : null; } - public function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $edgeName): ?Node + /** + * Find a single child node by its name + * + * @return Node|null the node that is connected to its parent with the specified $nodeName, or NULL if no matching node exists or the parent node is not accessible + */ + private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $nodeName): ?Node { $queryBuilder = $this->createQueryBuilder() ->select('cn.*, h.name, h.contentstreamid') @@ -239,8 +246,8 @@ public function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNod ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('h.name = :edgeName')->setParameter('edgeName', $edgeName->value); - $this->addRestrictionRelationConstraints($queryBuilder, 'cn'); + ->andWhere('h.name = :edgeName')->setParameter('edgeName', $nodeName->value); + $this->addRestrictionRelationConstraints($queryBuilder, 'cn', 'h', ':contentStreamId', ':dimensionSpacePointHash'); return $this->fetchNode($queryBuilder); } @@ -289,7 +296,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); - $this->addRestrictionRelationConstraints($queryBuilderInitial); + $this->addRestrictionRelationConstraints($queryBuilderInitial, 'n', 'h', ':contentStreamId', ':dimensionSpacePointHash'); $queryBuilderRecursive = $this->createQueryBuilder() ->select('c.*, h.name, h.contentstreamid, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') @@ -304,7 +311,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi if ($filter->nodeTypes !== null) { $this->addNodeTypeCriteria($queryBuilderRecursive, $filter->nodeTypes, 'c'); } - $this->addRestrictionRelationConstraints($queryBuilderRecursive, 'c'); + $this->addRestrictionRelationConstraints($queryBuilderRecursive, 'c', 'h', ':contentStreamId', ':dimensionSpacePointHash'); $queryBuilderCte = $this->createQueryBuilder() ->select('*') @@ -381,7 +388,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); - $this->addRestrictionRelationConstraints($queryBuilderInitial, 'n', 'ph'); + $this->addRestrictionRelationConstraints($queryBuilderInitial, 'n', 'ph', ':contentStreamId', ':dimensionSpacePointHash'); $queryBuilderRecursive = $this->createQueryBuilder() ->select('p.*, h.name, h.contentstreamid, h.parentnodeanchor') @@ -390,7 +397,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->innerJoin('p', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = p.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); - $this->addRestrictionRelationConstraints($queryBuilderRecursive, 'p'); + $this->addRestrictionRelationConstraints($queryBuilderRecursive, 'p', 'h', ':contentStreamId', ':dimensionSpacePointHash'); $queryBuilderCte = $this->createQueryBuilder() ->select('*') @@ -472,7 +479,6 @@ private function findNodeByPathFromStartingNode(NodePath $path, Node $startingNo $currentNode = $startingNode; foreach ($path->getParts() as $edgeName) { - // id exists here :) $currentNode = $this->findChildNodeConnectedThroughEdgeName($currentNode->nodeAggregateId, $edgeName); if ($currentNode === null) { return null; @@ -491,18 +497,32 @@ private function createUniqueParameterName(): string return 'param_' . (++$this->dynamicParameterCount); } - private function addRestrictionRelationConstraints(QueryBuilder $queryBuilder, string $nodeTableAlias = 'n', string $hierarchyRelationTableAlias = 'h'): void + /** + * @param QueryBuilder $queryBuilder + * @param string $nodeTableAlias + * @param string $hierarchyRelationTableAlias + * @param string|null $contentStreamIdParameter if not given, condition will be on the hierachy relation content stream id + * @param string|null $dimensionspacePointHashParameter if not given, condition will be on the hierachy relation dimension space point hash + * @return void + */ + private function addRestrictionRelationConstraints(QueryBuilder $queryBuilder, string $nodeTableAlias = 'n', string $hierarchyRelationTableAlias = 'h', ?string $contentStreamIdParameter = null, ?string $dimensionspacePointHashParameter = null): void { if ($this->visibilityConstraints->isDisabledContentShown()) { return; } + $nodeTablePrefix = $nodeTableAlias === '' ? '' : $nodeTableAlias . '.'; $hierarchyRelationTablePrefix = $hierarchyRelationTableAlias === '' ? '' : $hierarchyRelationTableAlias . '.'; + + $contentStreamIdCondition = 'r.contentstreamid = ' . ($contentStreamIdParameter ?? ($hierarchyRelationTablePrefix . 'contentstreamid')); + + $dimensionspacePointHashCondition = 'r.dimensionspacepointhash = ' . ($dimensionspacePointHashParameter ?? ($hierarchyRelationTablePrefix . 'dimensionspacepointhash')); + $subQueryBuilder = $this->createQueryBuilder() ->select('1') ->from($this->tableNamePrefix . '_restrictionrelation', 'r') - ->where('r.contentstreamid = ' . $hierarchyRelationTablePrefix . 'contentstreamid') - ->andWhere('r.dimensionspacepointhash = ' . $hierarchyRelationTablePrefix . 'dimensionspacepointhash') + ->where($contentStreamIdCondition) + ->andWhere($dimensionspacePointHashCondition) ->andWhere('r.affectednodeaggregateid = ' . $nodeTablePrefix . 'nodeaggregateid'); $queryBuilder->andWhere( 'NOT EXISTS (' . $subQueryBuilder->getSQL() . ')' @@ -620,7 +640,7 @@ private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, Fi if ($filter->propertyValue !== null) { $this->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); } - $this->addRestrictionRelationConstraints($queryBuilder); + $this->addRestrictionRelationConstraints($queryBuilder, 'n', 'h', ':contentStreamId', ':dimensionSpacePointHash'); return $queryBuilder; } @@ -640,8 +660,8 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('dh.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) ->andWhere('sh.contentstreamid = :contentStreamId'); - $this->addRestrictionRelationConstraints($queryBuilder, 'dn', 'dh'); - $this->addRestrictionRelationConstraints($queryBuilder, 'sn', 'sh'); + $this->addRestrictionRelationConstraints($queryBuilder, 'dn', 'dh', ':contentStreamId', ':dimensionSpacePointHash'); + $this->addRestrictionRelationConstraints($queryBuilder, 'sn', 'sh', ':contentStreamId', ':dimensionSpacePointHash'); if ($filter->nodeTypes !== null) { $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes, "{$destinationTablePrefix}n"); } @@ -707,7 +727,7 @@ private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNod ->andWhere('h.position ' . ($preceding ? '<' : '>') . ' (' . $siblingPositionSubQuery->getSQL() . ')') ->orderBy('h.position', $preceding ? 'DESC' : 'ASC'); - $this->addRestrictionRelationConstraints($queryBuilder); + $this->addRestrictionRelationConstraints($queryBuilder, 'n', 'h', ':contentStreamId', ':dimensionSpacePointHash'); if ($filter->nodeTypes !== null) { $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); } @@ -740,8 +760,8 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('c.nodeaggregateid = :entryNodeAggregateId'); - $this->addRestrictionRelationConstraints($queryBuilderInitial, 'n', 'ph'); - $this->addRestrictionRelationConstraints($queryBuilderInitial, 'c', 'ch'); + $this->addRestrictionRelationConstraints($queryBuilderInitial, 'n', 'ph', ':contentStreamId', ':dimensionSpacePointHash'); + $this->addRestrictionRelationConstraints($queryBuilderInitial, 'c', 'ch', ':contentStreamId', ':dimensionSpacePointHash'); $queryBuilderRecursive = $this->createQueryBuilder() ->select('p.*, h.name, h.contentstreamid, h.parentnodeanchor') @@ -750,7 +770,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->innerJoin('p', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = p.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); - $this->addRestrictionRelationConstraints($queryBuilderRecursive, 'p'); + $this->addRestrictionRelationConstraints($queryBuilderRecursive, 'p', 'h', ':contentStreamId', ':dimensionSpacePointHash'); $queryBuilderCte = $this->createQueryBuilder() ->select('*') @@ -782,7 +802,7 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('p.nodeaggregateid = :entryNodeAggregateId'); - $this->addRestrictionRelationConstraints($queryBuilderInitial); + $this->addRestrictionRelationConstraints($queryBuilderInitial, 'n', 'h', ':contentStreamId', ':dimensionSpacePointHash'); $queryBuilderRecursive = $this->createQueryBuilder() ->select('c.*, h.name, h.contentstreamid, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') @@ -791,7 +811,7 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate ->innerJoin('p', $this->tableNamePrefix . '_node', 'c', 'c.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); - $this->addRestrictionRelationConstraints($queryBuilderRecursive, 'c'); + $this->addRestrictionRelationConstraints($queryBuilderRecursive, 'c', 'h', ':contentStreamId', ':dimensionSpacePointHash'); $queryBuilderCte = $this->createQueryBuilder() ->select('*') diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentSubhypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentSubhypergraph.php index 32705839a97..cf3aafd476d 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentSubhypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentSubhypergraph.php @@ -269,8 +269,10 @@ public function findParentNode(NodeAggregateId $childNodeAggregateId): ?Node ) : null; } - public function findNodeByPath(NodePath $path, NodeAggregateId $startingNodeAggregateId): ?Node + public function findNodeByPath(NodePath|NodeName $path, NodeAggregateId $startingNodeAggregateId): ?Node { + $path = $path instanceof NodeName ? NodePath::fromNodeNames($path) : $path; + $startingNode = $this->findNodeById($startingNodeAggregateId); return $startingNode @@ -287,9 +289,9 @@ public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node : null; } - public function findChildNodeConnectedThroughEdgeName( + private function findChildNodeConnectedThroughEdgeName( NodeAggregateId $parentNodeAggregateId, - NodeName $edgeName + NodeName $nodeName ): ?Node { $query = HypergraphChildQuery::create( $this->contentStreamId, @@ -298,7 +300,7 @@ public function findChildNodeConnectedThroughEdgeName( ); $query = $query->withDimensionSpacePoint($this->dimensionSpacePoint) ->withRestriction($this->visibilityConstraints) - ->withChildNodeName($edgeName); + ->withChildNodeName($nodeName); $nodeRow = $query->execute($this->getDatabaseConnection())->fetchAssociative(); diff --git a/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/CRBehavioralTestsSubjectProvider.php b/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/CRBehavioralTestsSubjectProvider.php index bfcb0806fc9..24d758de0f4 100644 --- a/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/CRBehavioralTestsSubjectProvider.php +++ b/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/CRBehavioralTestsSubjectProvider.php @@ -36,7 +36,7 @@ trait CRBehavioralTestsSubjectProvider * A runtime cache of all content repositories already set up, represented by their ID * @var array */ - protected array $alreadySetUpContentRepositories = []; + protected static array $alreadySetUpContentRepositories = []; protected ?ContentRepository $currentContentRepository = null; @@ -169,8 +169,9 @@ protected function setUpContentRepository(ContentRepositoryId $contentRepository * Catch Up process and the testcase reset. */ $contentRepository = $this->createContentRepository($contentRepositoryId); - if (!in_array($contentRepository->id, $this->alreadySetUpContentRepositories)) { + if (!in_array($contentRepository->id, self::$alreadySetUpContentRepositories)) { $contentRepository->setUp(); + self::$alreadySetUpContentRepositories[] = $contentRepository->id; } /** @var EventStoreInterface $eventStore */ $eventStore = (new \ReflectionClass($contentRepository))->getProperty('eventStore')->getValue($contentRepository); diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindChildNodeConnectedThroughEdgeName.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPathAsNodeName.feature similarity index 79% rename from Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindChildNodeConnectedThroughEdgeName.feature rename to Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPathAsNodeName.feature index 67898ef2130..9125fd17850 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindChildNodeConnectedThroughEdgeName.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPathAsNodeName.feature @@ -1,5 +1,5 @@ @contentrepository @adapters=DoctrineDBAL,Postgres -Feature: Find nodes using the findChildNodeConnectedThroughEdgeName query +Feature: Find nodes using the findNodeByPath query with node name as path argument Background: Given using the following content dimensions: @@ -88,15 +88,15 @@ Feature: Find nodes using the findChildNodeConnectedThroughEdgeName query And the graph projection is fully up to date Scenario: - # findChildNodeConnectedThroughEdgeName queries without results - When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id "non-existing" and edge name "non-existing" I expect no node to be returned - When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id "home" and edge name "non-existing" I expect no node to be returned - When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id "non-existing" and edge name "home" I expect no node to be returned + # findNodeByPath queries without results + When I execute the findNodeByPath query for parent node aggregate id "non-existing" and node name "non-existing" as path I expect no node to be returned + When I execute the findNodeByPath query for parent node aggregate id "home" and node name "non-existing" as path I expect no node to be returned + When I execute the findNodeByPath query for parent node aggregate id "non-existing" and node name "home" as path I expect no node to be returned # node "a2a2" is disabled and should not be returned - When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id "a2a" and edge name "a2a2" I expect no node to be returned + When I execute the findNodeByPath query for parent node aggregate id "a2a" and node name "a2a2" as path I expect no node to be returned # node "a2a2" is disabled and should not lead to results if specified as parent node id - When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id "a2a2" and edge name "a2a2a" I expect no node to be returned + When I execute the findNodeByPath query for parent node aggregate id "a2a2" and node name "a2a2a" as path I expect no node to be returned - # findChildNodeConnectedThroughEdgeName queries with results - When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id "home" and edge name "contact" I expect the node "contact" to be returned - When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id "a2a" and edge name "a2a1" I expect the node "a2a1" to be returned + # findNodeByPath queries with results + When I execute the findNodeByPath query for parent node aggregate id "home" and node name "contact" as path I expect the node "contact" to be returned + When I execute the findNodeByPath query for parent node aggregate id "a2a" and node name "a2a1" as path I expect the node "a2a1" to be returned diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/Command/CreateNodeAggregateWithNode.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/Command/CreateNodeAggregateWithNode.php index 8f67052dd59..fe125c03c8d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/Command/CreateNodeAggregateWithNode.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/Command/CreateNodeAggregateWithNode.php @@ -25,10 +25,8 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** - * CreateNodeAggregateWithNode + * Creates a new node aggregate with a new node. * - * Creates a new node aggregate with a new node in the given `contentStreamId` - * with the given `nodeAggregateId` and `originDimensionSpacePoint`. * The node will be appended as child node of the given `parentNodeId` which must cover the given * `originDimensionSpacePoint`. * @@ -97,7 +95,7 @@ public function withInitialPropertyValues(PropertyValuesToWrite $newInitialPrope * a tethered node aggregate id, you need to generate the child node aggregate ids in advance. * * _Alternatively you would need to fetch the created tethered node first from the subgraph. - * {@see ContentSubgraphInterface::findChildNodeConnectedThroughEdgeName()}_ + * {@see ContentSubgraphInterface::findNodeByPath()}_ * * The helper method {@see NodeAggregateIdsByNodePaths::createForNodeType()} will generate recursively * node aggregate ids for every tethered child node: diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/Command/CreateNodeAggregateWithNodeAndSerializedProperties.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/Command/CreateNodeAggregateWithNodeAndSerializedProperties.php index bd9edf3a9e1..dadbd5f3e15 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/Command/CreateNodeAggregateWithNodeAndSerializedProperties.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/Command/CreateNodeAggregateWithNodeAndSerializedProperties.php @@ -30,7 +30,7 @@ * The properties of {@see CreateNodeAggregateWithNode} are directly serialized; and then this command * is called and triggers the actual processing. * - * @api commands are the write-API of the ContentRepository + * @internal implementation detail, use {@see CreateNodeAggregateWithNode} instead. */ final class CreateNodeAggregateWithNodeAndSerializedProperties implements CommandInterface, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Command/SetSerializedNodeProperties.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Command/SetSerializedNodeProperties.php index 03cd7bcfe8d..b929ce833e5 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Command/SetSerializedNodeProperties.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Command/SetSerializedNodeProperties.php @@ -24,11 +24,11 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** - * Set property values for a given node (internal implementation). + * Set property values for a given node. * * The property values contain the serialized types already, and include type information. * - * @api commands are the write-API of the ContentRepository + * @internal implementation detail, use {@see SetNodeProperties} instead. */ final class SetSerializedNodeProperties implements CommandInterface, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValue.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValue.php index 57ba6eaba4a..7e3aa1af39e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValue.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValue.php @@ -33,7 +33,7 @@ public function __construct( } /** - * @param array $valueAndType + * @param array{type:string,value:mixed} $valueAndType */ public static function fromArray(array $valueAndType): self { diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValues.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValues.php index 9b1f162e6fe..936e941d925 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValues.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/Dto/SerializedPropertyValues.php @@ -43,7 +43,7 @@ public static function createEmpty(): self } /** - * @param array $propertyValues + * @param array $propertyValues */ public static function fromArray(array $propertyValues): self { @@ -129,6 +129,9 @@ public function splitByScope(NodeType $nodeType): array ); } + /** + * @phpstan-assert-if-true !null $this->getProperty() + */ public function propertyExists(string $propertyName): bool { return isset($this->values[$propertyName]); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Command/SetSerializedNodeReferences.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Command/SetSerializedNodeReferences.php index 9404160a980..355281c63cb 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Command/SetSerializedNodeReferences.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Command/SetSerializedNodeReferences.php @@ -25,11 +25,11 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** - * Set property values for a given node (internal implementation). + * Set property values for a given node. * * The property values contain the serialized types already, and include type information. * - * @api commands are the write-API of the ContentRepository + * @internal implementation detail, use {@see SetNodeReferences} instead. */ final class SetSerializedNodeReferences implements CommandInterface, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/SerializedNodeReference.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/SerializedNodeReference.php index 8928a428377..6c090ad60ea 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/SerializedNodeReference.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/SerializedNodeReference.php @@ -20,7 +20,7 @@ /** * "Raw" / Serialized node reference as saved in the event log // in projections. * - * @api used as part of commands/events + * @internal */ final class SerializedNodeReference implements \JsonSerializable { diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/SerializedNodeReferences.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/SerializedNodeReferences.php index 85dc99aa39c..ecf9bf32f9e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/SerializedNodeReferences.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/Dto/SerializedNodeReferences.php @@ -21,7 +21,7 @@ * A collection of SerializedNodeReference objects, to be used when creating reference relations. * * @implements \IteratorAggregate - * @api used as part of commands/events + * @internal */ final readonly class SerializedNodeReferences implements \IteratorAggregate, \Countable, \JsonSerializable { diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index 0f68cba7042..09f97b8a12c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -197,9 +197,9 @@ private function handleChangeNodeAggregateType( $node->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() ); - $tetheredNode = $subgraph->findChildNodeConnectedThroughEdgeName( - $node->nodeAggregateId, - $tetheredNodeName + $tetheredNode = $subgraph->findNodeByPath( + $tetheredNodeName, + $node->nodeAggregateId ); if ($tetheredNode === null) { $tetheredNodeAggregateId = $command->tetheredDescendantNodeAggregateIds diff --git a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/Command/CreateRootNodeAggregateWithNode.php b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/Command/CreateRootNodeAggregateWithNode.php index 0e2ceab99de..ec9b7f83096 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/Command/CreateRootNodeAggregateWithNode.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/Command/CreateRootNodeAggregateWithNode.php @@ -70,7 +70,7 @@ public static function create(ContentStreamId $contentStreamId, NodeAggregateId * a tethered node aggregate id, you need to generate the child node aggregate ids in advance. * * _Alternatively you would need to fetch the created tethered node first from the subgraph. - * {@see ContentSubgraphInterface::findChildNodeConnectedThroughEdgeName()}_ + * {@see ContentSubgraphInterface::findNodeByPath()}_ * * The helper method {@see NodeAggregateIdsByNodePaths::createForNodeType()} will generate recursively * node aggregate ids for every tethered child node: diff --git a/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php b/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php new file mode 100644 index 00000000000..458b4eeada1 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php @@ -0,0 +1,129 @@ +setLength(64) + ->setCustomSchemaOption('charset', 'ascii') + ->setCustomSchemaOption('collation', 'ascii_general_ci'); + } + + /** + * The ContentStreamId is generally a UUID, therefore not a real "string" but at the moment a strified identifier, + * we can safely store it as binary string as the charset doesn't matter + * for the characters we use (they are all asscii). + * + * We should however reduce the allowed size to 36 like suggested + * here in the schema and as further improvement store the UUID in a more DB friendly format. + * + * @see ContentStreamId + */ + public static function columnForContentStreamId(string $columnName): Column + { + return (new Column($columnName, Type::getType(Types::STRING))) + ->setLength(36) + ->setCustomSchemaOption('charset', 'binary'); + } + + /** + * An anchorpoint can be used in a given projection to link two nodes, it is a purely internal identifier and should + * be as performant as possible in queries, the current code uses UUIDs, + * so we will store the stringified UUID as binary for now. + * + * A simpler and faster format would be preferable though, int or a shorter binary format if possible. Fortunately + * this is a pure projection information and therefore could change by replay. + */ + public static function columnForNodeAnchorPoint(string $columnName): Column + { + return (new Column($columnName, Type::getType(Types::BINARY))) + ->setLength(36) + ->setNotnull(true); + } + + /** + * DimensionSpacePoints are PHP objects that need to be serialized as JSON, therefore we store them as TEXT for now, + * with a sensible collation for case insensitive text search. + * + * Using a dedicated JSON column format should be considered for the future. + * + * @see DimensionSpacePoint + */ + public static function columnForDimensionSpacePoint(string $columnName): Column + { + return (new Column($columnName, Type::getType(Types::TEXT))) + ->setDefault('{}') + ->setCustomSchemaOption('collation', 'utf8mb4_unicode_520_ci'); + } + + /** + * The hash for a given dimension space point for better query performance. As this is a hash, the size and type of + * content is deterministic, a binary type can be used as the actual content is not so important. + * + * We could imrpove by actually storing the hash in binary form and shortening and fixing the length. + * + * @see DimensionSpacePoint + */ + public static function columnForDimensionSpacePointHash(string $columnName): Column + { + return (new Column($columnName, Type::getType(Types::BINARY))) + ->setLength(32) + ->setDefault(''); + } + + /** + * The NodeTypeName is an ascii string, we should be able to sort it properly, but we don't need unicode here. + * + * @see NodeTypeName + */ + public static function columnForNodeTypeName(string $columnName): Column + { + return (new Column($columnName, Type::getType(Types::STRING))) + ->setLength(255) + ->setNotnull(true) + ->setCustomSchemaOption('charset', 'ascii') + ->setCustomSchemaOption('collation', 'ascii_general_ci'); + } + + /** + * @param AbstractSchemaManager $schemaManager + * @param Table[] $tables + * @return Schema + */ + public static function createSchemaWithTables(AbstractSchemaManager $schemaManager, array $tables): Schema + { + $schemaConfig = $schemaManager->createSchemaConfig(); + $schemaConfig->setDefaultTableOptions([ + 'charset' => 'utf8mb4' + ]); + + return new Schema($tables, [], $schemaConfig); + } +} diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php index f1e861f3beb..6d6369eb3e8 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php @@ -420,7 +420,7 @@ public function getPropertyType(string $propertyName): string if (!$this->hasProperty($propertyName)) { throw new \InvalidArgumentException( - sprintf('NodeType schema has no property "%s" configured. Cannot read its type.', $propertyName), + sprintf('NodeType schema has no property "%s" configured for the NodeType "%s". Cannot read its type.', $propertyName, $this->name->value), 1695062252040 ); } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/AbsoluteNodePath.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/AbsoluteNodePath.php index d6ea986467b..1d36fa18c2d 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/AbsoluteNodePath.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/AbsoluteNodePath.php @@ -24,10 +24,19 @@ * Example: * root path: '/' results in * ~ {"rootNodeTypeName": "Neos.ContentRepository:Root", "path": []} - * non-root path: '//my/site' results in - * ~ {"rootNodeTypeName": "Neos.ContentRepository:Root", "path": ["my","site"]} + * non-root path: '//my-site/main' results in + * ~ {"rootNodeTypeName": "Neos.ContentRepository:Root", "path": ["my-site", "main"]} * * It describes the hierarchy path of a node to and including its root node in a subgraph. + * + * To fetch a node via an absolute path use the subgraph: {@see ContentSubgraphInterface::findNodeByAbsolutePath()} + * + * ```php + * $subgraph->findNodeByAbsolutePath( + * AbsoluteNodePath::fromString("//my-site/main") + * ) + * ``` + * * @api */ final class AbsoluteNodePath implements \JsonSerializable @@ -49,8 +58,14 @@ public static function fromRootNodeTypeNameAndRelativePath( } /** - * The ancestors must be ordered with the root node first, so if you call this using - * {@see ContentSubgraphInterface::findAncestorNodes()}, you need to call ${@see Nodes::reverse()} first + * The ancestors must be ordered with the root node first. + * + * If you want to retrieve the path of a node using {@see ContentSubgraphInterface::findAncestorNodes()}, you need to reverse the order first {@see Nodes::reverse()} + * + * ```php + * $ancestors = $this->findAncestorNodes($leafNode->nodeAggregateId, FindAncestorNodesFilter::create())->reverse(); + * $absoluteNodePath = AbsoluteNodePath::fromLeafNodeAndAncestors($leafNode, $ancestors); + * ``` */ public static function fromLeafNodeAndAncestors(Node $leafNode, Nodes $ancestors): self { diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphWithRuntimeCaches/ContentSubgraphWithRuntimeCaches.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphWithRuntimeCaches/ContentSubgraphWithRuntimeCaches.php index 7660d7c154d..496e55db2b6 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphWithRuntimeCaches/ContentSubgraphWithRuntimeCaches.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphWithRuntimeCaches/ContentSubgraphWithRuntimeCaches.php @@ -163,33 +163,18 @@ public function findParentNode(NodeAggregateId $childNodeAggregateId): ?Node return $parentNode; } - public function findNodeByPath(NodePath $path, NodeAggregateId $startingNodeAggregateId): ?Node + public function findNodeByPath(NodePath|NodeName $path, NodeAggregateId $startingNodeAggregateId): ?Node { - // TODO implement runtime caches + // TODO: implement runtime caches return $this->wrappedContentSubgraph->findNodeByPath($path, $startingNodeAggregateId); } public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node { - // TODO implement runtime caches + // TODO: implement runtime caches return $this->wrappedContentSubgraph->findNodeByAbsolutePath($path); } - public function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $edgeName): ?Node - { - $namedChildNodeCache = $this->inMemoryCache->getNamedChildNodeByNodeIdCache(); - if ($namedChildNodeCache->contains($parentNodeAggregateId, $edgeName)) { - return $namedChildNodeCache->get($parentNodeAggregateId, $edgeName); - } - $node = $this->wrappedContentSubgraph->findChildNodeConnectedThroughEdgeName($parentNodeAggregateId, $edgeName); - if ($node === null) { - return null; - } - $namedChildNodeCache->add($parentNodeAggregateId, $edgeName, $node); - $this->inMemoryCache->getNodeByNodeAggregateIdCache()->add($node->nodeAggregateId, $node); - return $node; - } - public function findSucceedingSiblingNodes(NodeAggregateId $siblingNodeAggregateId, FindSucceedingSiblingNodesFilter $filter): Nodes { // TODO implement runtime caches diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php index 5f65fb6a0f1..e3f5fe29b85 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php @@ -100,13 +100,6 @@ public function findSucceedingSiblingNodes(NodeAggregateId $siblingNodeAggregate */ public function findPrecedingSiblingNodes(NodeAggregateId $siblingNodeAggregateId, Filter\FindPrecedingSiblingNodesFilter $filter): Nodes; - /** - * Find a single child node by its name - * - * @return Node|null the node that is connected to its parent with the specified $edgeName, or NULL if no matching node exists or the parent node is not accessible - */ - public function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $edgeName): ?Node; - /** * Recursively find all nodes above the $entryNodeAggregateId that match the specified $filter and return them as a flat list */ @@ -181,10 +174,12 @@ public function countBackReferences(NodeAggregateId $nodeAggregateId, Filter\Cou /** * Find a single node underneath $startingNodeAggregateId that matches the specified $path * + * If a node name as $path is given it will be treated as path with a single segment. + * * NOTE: This operation is most likely to be deprecated since the concept of node paths is not really used in the core, and it has some logical issues * @return Node|null the node that matches the given $path, or NULL if no node on that path is accessible */ - public function findNodeByPath(NodePath $path, NodeAggregateId $startingNodeAggregateId): ?Node; + public function findNodeByPath(NodePath|NodeName $path, NodeAggregateId $startingNodeAggregateId): ?Node; /** * Find a single node underneath that matches the specified absolute $path @@ -197,7 +192,7 @@ public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node; /** * Determine the absolute path of a node * - * @deprecated use ${@see self::findAncestorNodes()} instead + * @deprecated use {@see self::findAncestorNodes()} in combination with {@see AbsoluteNodePath::fromLeafNodeAndAncestors()} instead * @throws \InvalidArgumentException if the node path could not be retrieved because it is inaccessible or contains no valid path. The latter can happen if any node in the hierarchy has no name */ public function retrieveNodePath(NodeAggregateId $nodeAggregateId): AbsoluteNodePath; diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index d29f4191ec9..55f6e39a563 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -29,22 +29,24 @@ * * The node does not have structure information, i.e. no infos * about its children. To f.e. fetch children, you need to fetch - * the subgraph via $node->subgraphIdentity and then - * call findChildNodes() on the subgraph. + * the subgraph {@see ContentGraphInterface::getSubgraph()} via + * $subgraphIdentity {@see Node::$subgraphIdentity}. and then + * call findChildNodes() {@see ContentSubgraphInterface::findChildNodes()} + * on the subgraph. * * @api Note: The constructor is not part of the public API */ final readonly class Node { /** - * @param ContentSubgraphIdentity $subgraphIdentity This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId} + * @param ContentSubgraphIdentity $subgraphIdentity This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId}. With this information, you can fetch a Subgraph using {@see ContentGraphInterface::getSubgraph()}. * @param NodeAggregateId $nodeAggregateId NodeAggregateId (identifier) of this node. This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId} * @param OriginDimensionSpacePoint $originDimensionSpacePoint The DimensionSpacePoint the node originates in. Usually needed to address a Node in a NodeAggregate in order to update it. * @param NodeAggregateClassification $classification The classification (regular, root, tethered) of this node * @param NodeTypeName $nodeTypeName The node's node type name; always set, even if unknown to the NodeTypeManager * @param NodeType|null $nodeType The node's node type, null if unknown to the NodeTypeManager - @deprecated Don't rely on this too much, as the capabilities of the NodeType here will probably change a lot; Ask the {@see NodeTypeManager} instead - * @param PropertyCollection $properties All properties of this node. References are NOT part of this API; To access references, {@see ContentSubgraphInterface::findReferences()} can be used; To read the serialized properties, call properties->serialized(). - * @param NodeName|null $nodeName The optional name of this node {@see ContentSubgraphInterface::findChildNodeConnectedThroughEdgeName()} + * @param PropertyCollection $properties All properties of this node. References are NOT part of this API; To access references, {@see ContentSubgraphInterface::findReferences()} can be used; To read the serialized properties use {@see PropertyCollection::serialized()}. + * @param NodeName|null $nodeName The optionally named hierarchy relation to the node's parent. * @param Timestamps $timestamps Creation and modification timestamps of this node */ private function __construct( @@ -69,10 +71,7 @@ public static function create(ContentSubgraphIdentity $subgraphIdentity, NodeAgg } /** - * Returns the specified property. - * - * If the node has a content object attached, the property will be fetched - * there if it is gettable. + * Returns the specified property, or null if it does not exist (or was set to null -> unset) * * @param string $propertyName Name of the property * @return mixed value of the property @@ -80,14 +79,15 @@ public static function create(ContentSubgraphIdentity $subgraphIdentity, NodeAgg */ public function getProperty(string $propertyName): mixed { - return $this->properties[$propertyName]; + return $this->properties->offsetGet($propertyName); } /** - * If this node has a property with the given name. Does NOT check the NodeType; but checks - * for a non-NULL property value. + * If this node has a property with the given name. It does not check if the property exists in the current NodeType schema. + * + * That means that {@see self::getProperty()} will not be null, except for the rare case the property deserializing returns null. * - * @param string $propertyName + * @param string $propertyName Name of the property * @return boolean * @api */ diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodePath.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodePath.php index 147f434598e..a6c755f109a 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodePath.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodePath.php @@ -17,13 +17,23 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeName; /** - * The relative node path is a collection of NodeNames. If it contains no elements, it is considered root. + * The relative node path is a collection of node names {@see NodeName}. If it contains no elements, it is considered root. * * Example: * root path: '' is resolved to [] - * non-root path: 'my/site' is resolved to ~ ['my', 'site'] + * non-root path: 'my-document/main' is resolved to ~ ['my-document', 'main'] * * It describes the hierarchy path of a node to an ancestor node in a subgraph. + * + * To fetch a node on a path use the subgraph: {@see ContentSubgraphInterface::findNodeByPath()} + * + * ```php + * $subgraph->findNodeByPath( + * NodePath::fromString("my-document/main"), + * $siteNodeAggregateId + * ) + * ``` + * * @api */ final class NodePath implements \JsonSerializable diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/PropertyCollection.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/PropertyCollection.php index f673a3cfc5d..5c736d72a24 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/PropertyCollection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/PropertyCollection.php @@ -24,7 +24,7 @@ * @implements \IteratorAggregate * @api This object should not be instantiated by 3rd parties, but it is part of the {@see Node} read model */ -final class PropertyCollection implements \ArrayAccess, \IteratorAggregate +final class PropertyCollection implements \ArrayAccess, \IteratorAggregate, \Countable { /** * Properties from Nodes @@ -56,14 +56,16 @@ public function offsetExists($offset): bool public function offsetGet($offset): mixed { - if (!isset($this->deserializedPropertyValuesRuntimeCache[$offset])) { - $serializedProperty = $this->serializedPropertyValues->getProperty($offset); - $this->deserializedPropertyValuesRuntimeCache[$offset] = $serializedProperty === null - ? null - : $this->propertyConverter->deserializePropertyValue($serializedProperty); + if (array_key_exists($offset, $this->deserializedPropertyValuesRuntimeCache)) { + return $this->deserializedPropertyValuesRuntimeCache[$offset]; } - return $this->deserializedPropertyValuesRuntimeCache[$offset]; + $serializedProperty = $this->serializedPropertyValues->getProperty($offset); + if ($serializedProperty === null) { + return null; + } + return $this->deserializedPropertyValuesRuntimeCache[$offset] = + $this->propertyConverter->deserializePropertyValue($serializedProperty); } public function offsetSet($offset, $value): never @@ -90,4 +92,9 @@ public function serialized(): SerializedPropertyValues { return $this->serializedPropertyValues; } + + public function count(): int + { + return count($this->serializedPropertyValues); + } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjection.php b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjection.php index 74b9ab9907a..8c568ad961a 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamProjection.php @@ -16,8 +16,12 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Comparator; use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\SchemaConfig; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamAndNodeAggregateId; @@ -34,6 +38,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; +use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -89,22 +94,17 @@ private function setupTables(): void $connection->executeStatement(sprintf("UPDATE %s SET state='FORKED' WHERE state='REBASING'; ", $this->tableName)); } - $schema = new Schema(); - $contentStreamTable = $schema->createTable($this->tableName); - $contentStreamTable->addColumn('contentStreamId', Types::STRING) - ->setLength(40) - ->setNotnull(true); - $contentStreamTable->addColumn('version', Types::INTEGER) - ->setNotnull(true); - $contentStreamTable->addColumn('sourceContentStreamId', Types::STRING) - ->setLength(40) - ->setNotnull(false); - $contentStreamTable->addColumn('state', Types::STRING) - ->setLength(20) - ->setNotnull(true); - $contentStreamTable->addColumn('removed', Types::BOOLEAN) - ->setDefault(false) - ->setNotnull(false); + $schema = DbalSchemaFactory::createSchemaWithTables($schemaManager, [ + (new Table($this->tableName, [ + DbalSchemaFactory::columnForContentStreamId('contentStreamId')->setNotnull(true), + (new Column('version', Type::getType(Types::INTEGER)))->setNotnull(true), + DbalSchemaFactory::columnForContentStreamId('sourceContentStreamId')->setNotnull(false), + // Should become a DB ENUM (unclear how to configure with DBAL) or int (latter needs adaption to code) + (new Column('state', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(true), + (new Column('removed', Type::getType(Types::BOOLEAN)))->setDefault(false)->setNotnull(false) + ])) + ]); + $schemaDiff = (new Comparator())->compare($schemaManager->createSchema(), $schema); foreach ($schemaDiff->toSaveSql($connection->getDatabasePlatform()) as $statement) { $connection->executeStatement($statement); diff --git a/Neos.ContentRepository.Core/Classes/Projection/NodeHiddenState/NodeHiddenStateProjection.php b/Neos.ContentRepository.Core/Classes/Projection/NodeHiddenState/NodeHiddenStateProjection.php index 3b83ff11b60..8fe377c4054 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/NodeHiddenState/NodeHiddenStateProjection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/NodeHiddenState/NodeHiddenStateProjection.php @@ -16,8 +16,10 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Comparator; -use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; @@ -25,6 +27,7 @@ use Neos\ContentRepository\Core\Feature\NodeDisabling\Event\NodeAggregateWasDisabled; use Neos\ContentRepository\Core\Feature\NodeDisabling\Event\NodeAggregateWasEnabled; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; +use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\EventStore\CatchUp\CheckpointStorageInterface; use Neos\EventStore\DoctrineAdapter\DoctrineCheckpointStorage; @@ -65,27 +68,20 @@ private function setupTables(): void if (!$schemaManager instanceof AbstractSchemaManager) { throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - $schema = new Schema(); - $contentStreamTable = $schema->createTable($this->tableName); - $contentStreamTable->addColumn('contentstreamid', Types::STRING) - ->setLength(40) - ->setNotnull(true); - $contentStreamTable->addColumn('nodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(true); - $contentStreamTable->addColumn('dimensionspacepointhash', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $contentStreamTable->addColumn('dimensionspacepoint', Types::TEXT) - ->setNotnull(false); - $contentStreamTable->addColumn('hidden', Types::BOOLEAN) - ->setDefault(false) - ->setNotnull(false); - - $contentStreamTable->setPrimaryKey( + + $nodeHiddenStateTable = new Table($this->tableName, [ + DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotNull(true), + DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotNull(false), + DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash')->setNotNull(false), + DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotNull(false), + (new Column('hidden', Type::getType(Types::BOOLEAN)))->setDefault(false)->setNotnull(false) + ]); + $nodeHiddenStateTable->setPrimaryKey( ['contentstreamid', 'nodeaggregateid', 'dimensionspacepointhash'] ); + $schema = DbalSchemaFactory::createSchemaWithTables($schemaManager, [$nodeHiddenStateTable]); + $schemaDiff = (new Comparator())->compare($schemaManager->createSchema(), $schema); foreach ($schemaDiff->toSaveSql($connection->getDatabasePlatform()) as $statement) { $connection->executeStatement($statement); diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php b/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php index 35a1834ad11..85ed95bbc4d 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php @@ -16,8 +16,10 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Comparator; -use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\RootWorkspaceWasCreated; @@ -33,6 +35,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; +use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -48,6 +51,8 @@ */ class WorkspaceProjection implements ProjectionInterface, WithMarkStaleInterface { + private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; + /** * @var WorkspaceFinder|null Cache for the workspace finder returned by {@see getState()}, * so that always the same instance is returned @@ -82,32 +87,18 @@ private function setupTables(): void throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - $schema = new Schema(); - $workspaceTable = $schema->createTable($this->tableName); - $workspaceTable->addColumn('workspacename', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $workspaceTable->addColumn('baseworkspacename', Types::STRING) - ->setLength(255) - ->setNotnull(false); - $workspaceTable->addColumn('workspacetitle', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $workspaceTable->addColumn('workspacedescription', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $workspaceTable->addColumn('workspaceowner', Types::STRING) - ->setLength(255) - ->setNotnull(false); - $workspaceTable->addColumn('currentcontentstreamid', Types::STRING) - ->setLength(40) - ->setNotnull(false); - $workspaceTable->addColumn('status', Types::STRING) - ->setLength(50) - ->setNotnull(false); - + $workspaceTable = new Table($this->tableName, [ + (new Column('workspacename', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + (new Column('baseworkspacename', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + (new Column('workspacetitle', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + (new Column('workspacedescription', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + (new Column('workspaceowner', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + DbalSchemaFactory::columnForContentStreamId('currentcontentstreamid')->setNotNull(true), + (new Column('status', Type::getType(Types::STRING)))->setLength(20)->setNotnull(false)->setCustomSchemaOption('charset', 'binary') + ]); $workspaceTable->setPrimaryKey(['workspacename']); + $schema = DbalSchemaFactory::createSchemaWithTables($schemaManager, [$workspaceTable]); $schemaDiff = (new Comparator())->compare($schemaManager->createSchema(), $schema); foreach ($schemaDiff->toSaveSql($connection->getDatabasePlatform()) as $statement) { $connection->executeStatement($statement); diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeName.php b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeName.php index 017ab717eba..5d64588e5ec 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeName.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeName.php @@ -15,13 +15,25 @@ namespace Neos\ContentRepository\Core\SharedModel\Node; use Behat\Transliterator\Transliterator; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; /** - * The Node name is the "path part" of the node; i.e. when accessing the node "/foo" via path, + * The Node name is the "path part" of the node; i.e. when accessing the node "/foo" via path {@see NodePath}, * the node name is "foo". * * Semantically it describes the hierarchical relation of a node to its parent, e.g. "main" denotes the main child node. * + * Multiple node names describe a node path {@see NodePath} + * + * To fetch the child node that is connected with the parent via the name "main" use the subgraph's: {@see ContentSubgraphInterface::findNodeByPath()} + * + * ```php + * $subgraph->findNodeByPath( + * NodeName::fromString("main"), + * $parentNodeAggregateId + * ) + * ``` + * * @api */ final class NodeName implements \JsonSerializable diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToAssetsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToAssetsProcessor.php index f96db3b58cc..808ce0f40f7 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToAssetsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToAssetsProcessor.php @@ -62,7 +62,12 @@ public function run(): ProcessorResult continue; } foreach ($properties as $propertyName => $propertyValue) { - $propertyType = $nodeType->getPropertyType($propertyName); + try { + $propertyType = $nodeType->getPropertyType($propertyName); + } catch (\InvalidArgumentException $exception) { + $this->dispatch(Severity::WARNING, 'Skipped node data processing for the property "%s". The property name is not part of the NodeType schema for the NodeType "%s". (Node: %s)', $propertyName, $nodeType->name->value, $nodeDataRow['identifier']); + continue; + } foreach ($this->extractAssetIdentifiers($propertyType, $propertyValue) as $assetId) { if (array_key_exists($assetId, $this->processedAssetIds)) { continue; diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php index cc3ad13609b..4ea8a74d16f 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php @@ -316,7 +316,12 @@ public function extractPropertyValuesAndReferences(array $nodeDataRow, NodeType } foreach ($decodedProperties as $propertyName => $propertyValue) { - $type = $nodeType->getPropertyType($propertyName); + try { + $type = $nodeType->getPropertyType($propertyName); + } catch (\InvalidArgumentException $exception) { + $this->dispatch(Severity::WARNING, 'Skipped node data processing for the property "%s". The property name is not part of the NodeType schema for the NodeType "%s". (Node: %s)', $propertyName, $nodeType->name->value, $nodeDataRow['identifier']); + continue; + } if ($type === 'reference' || $type === 'references') { if (!empty($propertyValue)) { diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php index 0054ab25685..ac0db3ac9dc 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php @@ -62,6 +62,11 @@ class FeatureContext implements Context */ private array $loggedErrors = []; + /** + * @var array + */ + private array $loggedWarnings = []; + private ContentRepository $contentRepository; protected ContentRepositoryRegistry $contentRepositoryRegistry; @@ -141,6 +146,8 @@ public function iRunTheEventMigration(string $contentStream = null): void $migration->onMessage(function (Severity $severity, string $message) { if ($severity === Severity::ERROR) { $this->loggedErrors[] = $message; + } elseif ($severity === Severity::WARNING) { + $this->loggedWarnings[] = $message; } }); $this->lastMigrationResult = $migration->run(); @@ -186,12 +193,21 @@ public function iExpectTheFollowingEventsToBeExported(TableNode $table): void /** * @Then I expect the following errors to be logged */ - public function iExpectTheFollwingErrorsToBeLogged(TableNode $table): void + public function iExpectTheFollowingErrorsToBeLogged(TableNode $table): void { Assert::assertSame($table->getColumn(0), $this->loggedErrors, 'Expected logged errors do not match'); $this->loggedErrors = []; } + /** + * @Then I expect the following warnings to be logged + */ + public function iExpectTheFollowingWarningsToBeLogged(TableNode $table): void + { + Assert::assertSame($table->getColumn(0), $this->loggedWarnings, 'Expected logged warnings do not match'); + $this->loggedWarnings = []; + } + /** * @Then I expect a MigrationError * @Then I expect a MigrationError with the message @@ -307,6 +323,8 @@ public function findAssetById(string $assetId): SerializedAsset|SerializedImageV $migration->onMessage(function (Severity $severity, string $message) { if ($severity === Severity::ERROR) { $this->loggedErrors[] = $message; + } elseif ($severity === Severity::WARNING) { + $this->loggedWarnings[] = $message; } }); $this->lastMigrationResult = $migration->run(); diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Assets.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Assets.feature index f4ca3e07455..52ac42afe7e 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Assets.feature +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Assets.feature @@ -43,9 +43,9 @@ Feature: Export of used Assets, Image Variants and Persistent Resources Scenario: Exporting an Image Variant includes the original Image asset as well When I have the following node data rows: - | Identifier | Path | Node Type | Properties | - | sites-node-id | /sites | unstructured | | - | site-node-id | /sites/test-site | Some.Package:Homepage | {"string": "asset:\/\/variant1"} | + | Identifier | Path | Node Type | Properties | + | sites-node-id | /sites | unstructured | | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"string": "asset:\/\/variant1"} | And I run the asset migration Then I expect the following Assets to be exported: """ @@ -87,12 +87,12 @@ Feature: Export of used Assets, Image Variants and Persistent Resources Scenario: Assets and image variants are only exported once each When I have the following node data rows: - | Identifier | Path | Node Type | Dimension Values | Properties | - | sites-node-id | /sites | unstructured | | | - | site-node-id | /sites/test-site | Some.Package:Homepage | | {"string": "asset:\/\/asset1"} | - | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["ch"]} | {"image": {"__flow_object_type": "Neos\\Media\\Domain\\Model\\Image", "__identifier": "asset2"}} | - | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["en"]} | {"assets": [{"__flow_object_type": "Neos\\Media\\Domain\\Model\\Document", "__identifier": "asset3"}, {"__flow_object_type": "Neos\\Media\\Domain\\Model\\Image", "__identifier": "asset2"}, {"__flow_object_type": "Neos\\Media\\Domain\\Model\\ImageVariant", "__identifier": "variant1"}]} | - | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} | {"string": "some text with an asset link"} | + | Identifier | Path | Node Type | Dimension Values | Properties | + | sites-node-id | /sites | unstructured | | | + | site-node-id | /sites/test-site | Some.Package:Homepage | | {"string": "asset:\/\/asset1"} | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["ch"]} | {"image": {"__flow_object_type": "Neos\\Media\\Domain\\Model\\Image", "__identifier": "asset2"}} | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["en"]} | {"assets": [{"__flow_object_type": "Neos\\Media\\Domain\\Model\\Document", "__identifier": "asset3"}, {"__flow_object_type": "Neos\\Media\\Domain\\Model\\Image", "__identifier": "asset2"}, {"__flow_object_type": "Neos\\Media\\Domain\\Model\\ImageVariant", "__identifier": "variant1"}]} | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"language": ["de"]} | {"string": "some text with an asset link"} | And I run the asset migration Then I expect the following Assets to be exported: """ @@ -178,12 +178,22 @@ Feature: Export of used Assets, Image Variants and Persistent Resources Scenario: Referring to non-existing asset When I have the following node data rows: - | Identifier | Path | Node Type | Properties | - | sites-node-id | /sites | unstructured | | - | site-node-id | /sites/test-site | Some.Package:Homepage | {"string": "asset:\/\/non-existing-asset"} | + | Identifier | Path | Node Type | Properties | + | sites-node-id | /sites | unstructured | | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"string": "asset:\/\/non-existing-asset"} | And I run the asset migration Then I expect no Assets to be exported And I expect no ImageVariants to be exported And I expect no PersistentResources to be exported And I expect the following errors to be logged | Failed to extract assets of property "string" of node "site-node-id" (type: "Some.Package:Homepage"): Failed to find mock asset with id "non-existing-asset" | + + Scenario: Nodes with properties that are not part of the node type schema (see https://github.com/neos/neos-development-collection/issues/4804) + When I have the following node data rows: + | Identifier | Path | Node Type | Properties | + | sites-node-id | /sites | unstructured | | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"unknownProperty": "asset:\/\/variant1"} | + And I run the asset migration + Then I expect no Assets to be exported + And I expect the following warnings to be logged + | Skipped node data processing for the property "unknownProperty". The property name is not part of the NodeType schema for the NodeType "Some.Package:Homepage". (Node: site-node-id) | diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/References.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/References.feature index 52e659f6017..8cd77523574 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/References.feature +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/References.feature @@ -36,12 +36,12 @@ Feature: Migrations that contain nodes with "reference" or "references propertie | c | /sites/site/c | Some.Package:SomeNodeType | {"text": "This is c", "refs": ["a", "b"]} | And I run the event migration Then I expect the following events to be exported - | Type | Payload | - | RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "c"} | + | Type | Payload | + | RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "c"} | | NodeReferencesWereSet | {"sourceNodeAggregateId":"a","affectedSourceOriginDimensionSpacePoints":[[]],"referenceName":"ref","references":{"b":{"targetNodeAggregateId":"b","properties":null}}} | | NodeReferencesWereSet | {"sourceNodeAggregateId":"c","affectedSourceOriginDimensionSpacePoints":[[]],"referenceName":"refs","references":{"a":{"targetNodeAggregateId":"a","properties":null},"b":{"targetNodeAggregateId":"b","properties":null}}} | @@ -60,13 +60,26 @@ Feature: Migrations that contain nodes with "reference" or "references propertie | c | /sites/site/c | Some.Package:SomeNodeType | {"language": ["ch"]} | {"text": "This is c", "refs": ["a", "b"]} | And I run the event migration Then I expect the following events to be exported - | Type | Payload | - | RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site"} | - | NodePeerVariantWasCreated | {} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "c"} | + | Type | Payload | + | RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site"} | + | NodePeerVariantWasCreated | {} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "c"} | | NodeReferencesWereSet | {"sourceNodeAggregateId":"a","affectedSourceOriginDimensionSpacePoints":[{"language": "en"}],"referenceName":"ref","references":{"b":{"targetNodeAggregateId":"b","properties":null}}} | | NodeReferencesWereSet | {"sourceNodeAggregateId":"b","affectedSourceOriginDimensionSpacePoints":[{"language": "de"}],"referenceName":"ref","references":{"a":{"targetNodeAggregateId":"a","properties":null}}} | | NodeReferencesWereSet | {"sourceNodeAggregateId":"c","affectedSourceOriginDimensionSpacePoints":[{"language": "ch"}],"referenceName":"refs","references":{"a":{"targetNodeAggregateId":"a","properties":null},"b":{"targetNodeAggregateId":"b","properties":null}}} | + + Scenario: Nodes with properties that are not part of the node type schema (see https://github.com/neos/neos-development-collection/issues/4804) + When I have the following node data rows: + | Identifier | Path | Node Type | Properties | + | sites-node-id | /sites | unstructured | | + | site-node-id | /sites/test-site | Some.Package:Homepage | {"unknownProperty": "ref"} | + And I run the event migration + Then I expect the following events to be exported + | Type | Payload | + | RootNodeAggregateWithNodeWasCreated | {"nodeAggregateId": "sites-node-id"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site-node-id"} | + And I expect the following warnings to be logged + | Skipped node data processing for the property "unknownProperty". The property name is not part of the NodeType schema for the NodeType "Some.Package:Homepage". (Node: site-node-id) | diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/ChildrenOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/ChildrenOperation.php index c9bfd37be60..cce0d558a0a 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/ChildrenOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/ChildrenOperation.php @@ -12,6 +12,7 @@ */ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria; use Neos\ContentRepository\Core\NodeType\NodeTypeNames; @@ -136,18 +137,13 @@ protected function earlyOptimizationOfFilters(FlowQuery $flowQuery, array $parse // Optimize property name filter if present if (isset($filter['PropertyNameFilter']) || isset($filter['PathFilter'])) { $nodePath = $filter['PropertyNameFilter'] ?? $filter['PathFilter']; - $nodePathSegments = explode('/', $nodePath); /** @var Node $contextNode */ foreach ($flowQuery->getContext() as $contextNode) { - $currentPathSegments = $nodePathSegments; - $resolvedNode = $contextNode; - while (($nodePathSegment = array_shift($currentPathSegments)) && !is_null($resolvedNode)) { - $resolvedNode = $this->contentRepositoryRegistry->subgraphForNode($resolvedNode) - ->findChildNodeConnectedThroughEdgeName( - $resolvedNode->nodeAggregateId, - NodeName::fromString($nodePathSegment) + $resolvedNode = $this->contentRepositoryRegistry->subgraphForNode($contextNode) + ->findNodeByPath( + NodePath::fromString($nodePath), + $contextNode->nodeAggregateId, ); - } if (!is_null($resolvedNode) && !isset($filteredOutputNodeIdentifiers[ $resolvedNode->nodeAggregateId->value diff --git a/Neos.ContentRepository.NodeMigration/src/Transformation/StripTagsOnPropertyTransformationFactory.php b/Neos.ContentRepository.NodeMigration/src/Transformation/StripTagsOnPropertyTransformationFactory.php index 1484827576b..405fd46196f 100644 --- a/Neos.ContentRepository.NodeMigration/src/Transformation/StripTagsOnPropertyTransformationFactory.php +++ b/Neos.ContentRepository.NodeMigration/src/Transformation/StripTagsOnPropertyTransformationFactory.php @@ -53,10 +53,9 @@ public function execute( DimensionSpacePointSet $coveredDimensionSpacePoints, ContentStreamId $contentStreamForWriting ): ?CommandResult { - if ($node->hasProperty($this->propertyName)) { - $properties = $node->properties; - /** @var SerializedPropertyValue $serializedPropertyValue safe since Node::hasProperty */ - $serializedPropertyValue = $properties->serialized()->getProperty($this->propertyName); + $properties = $node->properties->serialized(); + if ($properties->propertyExists($this->propertyName)) { + $serializedPropertyValue = $properties->getProperty($this->propertyName); $propertyValue = $serializedPropertyValue->value; if (!is_string($propertyValue)) { throw new \Exception( diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index 2883857ff9b..c4dc2eebf33 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -11,6 +11,7 @@ use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMappings; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; @@ -73,9 +74,9 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() ); - $tetheredNode = $subgraph->findChildNodeConnectedThroughEdgeName( + $tetheredNode = $subgraph->findNodeByPath( + $tetheredNodeName, $nodeAggregate->nodeAggregateId, - $tetheredNodeName ); if ($tetheredNode === null) { $foundMissingOrDisallowedTetheredNodes = true; diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index 6013ed435f0..b4e6246665b 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -21,6 +21,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\User\UserId; @@ -161,9 +162,9 @@ public function getCurrentSubgraph(): ContentSubgraphInterface */ public function iRememberNodeAggregateIdOfNodesChildAs(string $parentNodeAggregateId, string $childNodeName, string $indexName): void { - $this->rememberedNodeAggregateIds[$indexName] = $this->getCurrentSubgraph()->findChildNodeConnectedThroughEdgeName( + $this->rememberedNodeAggregateIds[$indexName] = $this->getCurrentSubgraph()->findNodeByPath( + NodePath::fromString($childNodeName), NodeAggregateId::fromString($parentNodeAggregateId), - NodeName::fromString($childNodeName) )->nodeAggregateId; } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index 304d9cab357..48335274b84 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -80,15 +80,13 @@ trait CRTestSuiteTrait use WorkspaceDiscarding; use WorkspacePublishing; - protected function setupCRTestSuiteTrait(bool $alwaysRunCrSetup = false): void + protected function setupCRTestSuiteTrait(): void { if (getenv('CATCHUPTRIGGER_ENABLE_SYNCHRONOUS_OPTION')) { CatchUpTriggerWithSynchronousOption::enableSynchronicityForSpeedingUpTesting(); } } - private static bool $wasContentRepositorySetupCalled = false; - /** * @BeforeScenario * @throws \Exception diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php index 8f9680a2736..32e5de9f0fd 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/GenericCommandExecutionAndEventPublication.php @@ -220,7 +220,8 @@ public function eventNumberIs(int $eventNumber, string $eventType, TableNode $pa $key = $assertionTableRow['Key']; $actualValue = Arrays::getValueByPath($actualEventPayload, $key); - if ($key === 'affectedDimensionSpacePoints') { + // Note: For dimension space points we switch to an array comparison because the order is not deterministic (@see https://github.com/neos/neos-development-collection/issues/4769) + if ($key === 'affectedDimensionSpacePoints' || $key === 'affectedOccupiedDimensionSpacePoints') { $expected = DimensionSpacePointSet::fromJsonString($assertionTableRow['Expected']); $actual = DimensionSpacePointSet::fromArray($actualValue); Assert::assertTrue($expected->equals($actual), 'Actual Dimension Space Point set "' . json_encode($actualValue) . '" does not match expected Dimension Space Point set "' . $assertionTableRow['Expected'] . '"'); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php index 1e0323c69d2..168a6f7a055 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php @@ -157,16 +157,16 @@ public function iExecuteTheFindNodeByAbsolutePathQueryIExpectTheFollowingNodes(s } /** - * @When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id :parentNodeIdSerialized and edge name :edgeNameSerialized I expect no node to be returned - * @When I execute the findChildNodeConnectedThroughEdgeName query for parent node aggregate id :parentNodeIdSerialized and edge name :edgeNameSerialized I expect the node :expectedNodeIdSerialized to be returned + * @When I execute the findNodeByPath query for parent node aggregate id :parentNodeIdSerialized and node name :edgeNameSerialized as path I expect no node to be returned + * @When I execute the findNodeByPath query for parent node aggregate id :parentNodeIdSerialized and node name :edgeNameSerialized as path I expect the node :expectedNodeIdSerialized to be returned */ - public function iExecuteTheFindChildNodeConnectedThroughEdgeNameQueryIExpectTheFollowingNodes(string $parentNodeIdSerialized, string $edgeNameSerialized, string $expectedNodeIdSerialized = null): void + public function iExecuteTheFindChildNodeByNodeNameQueryIExpectTheFollowingNodes(string $parentNodeIdSerialized, string $edgeNameSerialized, string $expectedNodeIdSerialized = null): void { $parentNodeAggregateId = NodeAggregateId::fromString($parentNodeIdSerialized); $edgeName = NodeName::fromString($edgeNameSerialized); $expectedNodeAggregateId = $expectedNodeIdSerialized !== null ? NodeAggregateId::fromString($expectedNodeIdSerialized) : null; - $actualNode = $this->getCurrentSubgraph()->findChildNodeConnectedThroughEdgeName($parentNodeAggregateId, $edgeName); + $actualNode = $this->getCurrentSubgraph()->findNodeByPath($edgeName, $parentNodeAggregateId); Assert::assertSame($actualNode?->nodeAggregateId->value, $expectedNodeAggregateId?->value); } @@ -263,7 +263,8 @@ public function iExecuteTheFindDescendantNodesQueryIExpectTheFollowingNodes(stri $subgraph = $this->getCurrentSubgraph(); $actualNodeIds = array_map(static fn(Node $node) => $node->nodeAggregateId->value, iterator_to_array($subgraph->findDescendantNodes($entryNodeAggregateId, $filter))); - Assert::assertSame($expectedNodeIds, $actualNodeIds, 'findDescendantNodes returned an unexpected result'); + // Note: In contrast to other similar checks, in this case we use assertEqualsCanonicalizing() instead of assertSame() because the order of descendant nodes is not completely deterministic (@see https://github.com/neos/neos-development-collection/issues/4769) + Assert::assertEqualsCanonicalizing($expectedNodeIds, $actualNodeIds, 'findDescendantNodes returned an unexpected result'); $actualCount = $subgraph->countDescendantNodes($entryNodeAggregateId, CountDescendantNodesFilter::fromFindDescendantNodesFilter($filter)); Assert::assertSame($expectedTotalCount ?? count($expectedNodeIds), $actualCount, 'countDescendantNodes returned an unexpected result'); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index e0abd0e1d3c..5ba244abcd9 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -456,7 +456,7 @@ public function iExpectThisNodeToBeTheChildOfNode(string $serializedParentNodeDi Assert::assertTrue($expectedParentDiscriminator->equals($actualParentDiscriminator), 'Parent discriminator does not match. Expected was ' . json_encode($expectedParentDiscriminator) . ', given was ' . json_encode($actualParentDiscriminator)); $expectedChildDiscriminator = NodeDiscriminator::fromNode($currentNode); - $child = $subgraph->findChildNodeConnectedThroughEdgeName($parent->nodeAggregateId, $currentNode->nodeName); + $child = $subgraph->findNodeByPath($currentNode->nodeName, $parent->nodeAggregateId); $actualChildDiscriminator = NodeDiscriminator::fromNode($child); Assert::assertTrue($expectedChildDiscriminator->equals($actualChildDiscriminator), 'Child discriminator does not match. Expected was ' . json_encode($expectedChildDiscriminator) . ', given was ' . json_encode($actualChildDiscriminator)); }); diff --git a/Neos.ContentRepositoryRegistry.TestSuite/Classes/Behavior/CRRegistrySubjectProvider.php b/Neos.ContentRepositoryRegistry.TestSuite/Classes/Behavior/CRRegistrySubjectProvider.php index 104fe983c8a..6bacd50a538 100644 --- a/Neos.ContentRepositoryRegistry.TestSuite/Classes/Behavior/CRRegistrySubjectProvider.php +++ b/Neos.ContentRepositoryRegistry.TestSuite/Classes/Behavior/CRRegistrySubjectProvider.php @@ -34,7 +34,7 @@ trait CRRegistrySubjectProvider /** * @var array */ - protected array $alreadySetUpContentRepositories = []; + protected static array $alreadySetUpContentRepositories = []; /** * @template T of object @@ -62,8 +62,9 @@ public function iInitializeContentRepository(string $contentRepositoryId): void $eventTableName = sprintf('cr_%s_events', $contentRepositoryId); $databaseConnection->executeStatement('TRUNCATE ' . $eventTableName); - if (!in_array($contentRepository->id, $this->alreadySetUpContentRepositories)) { + if (!in_array($contentRepository->id, self::$alreadySetUpContentRepositories)) { $contentRepository->setUp(); + self::$alreadySetUpContentRepositories[] = $contentRepository->id; } $contentRepository->resetProjectionStates(); } diff --git a/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml b/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml index bab78f378aa..650abaaff54 100644 --- a/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml +++ b/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml @@ -1,5 +1,11 @@ Neos: Flow: + persistence: + doctrine: + migrations: + ignoredTables: + 'cr_.*': true + # Improve debug output for node objects by ignoring large classes error: debugger: diff --git a/Neos.Media.Browser/Classes/Controller/AssetController.php b/Neos.Media.Browser/Classes/Controller/AssetController.php index 7f78d961061..f15304dfa1b 100644 --- a/Neos.Media.Browser/Classes/Controller/AssetController.php +++ b/Neos.Media.Browser/Classes/Controller/AssetController.php @@ -14,6 +14,7 @@ use Doctrine\Common\Persistence\Proxy as DoctrineProxy; use Doctrine\ORM\EntityNotFoundException; +use enshrined\svgSanitize\Sanitizer; use Neos\Error\Messages\Error; use Neos\Error\Messages\Message; use Neos\Flow\Annotations as Flow; @@ -35,6 +36,7 @@ use Neos\Media\Domain\Model\AssetCollection; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Model\AssetSource\AssetNotFoundExceptionInterface; +use Neos\Media\Domain\Model\AssetSource\AssetProxy\AssetProxyInterface; use Neos\Media\Domain\Model\AssetSource\AssetProxyRepositoryInterface; use Neos\Media\Domain\Model\AssetSource\AssetSourceConnectionExceptionInterface; use Neos\Media\Domain\Model\AssetSource\AssetSourceInterface; @@ -370,7 +372,8 @@ public function showAction(string $assetSourceIdentifier, string $assetProxyIden $this->view->assignMultiple([ 'assetProxy' => $assetProxy, - 'assetCollections' => $this->assetCollectionRepository->findAll() + 'assetCollections' => $this->assetCollectionRepository->findAll(), + 'assetContainsMaliciousContent' => $this->checkForMaliciousContent($assetProxy) ]); } catch (AssetNotFoundExceptionInterface | AssetSourceConnectionExceptionInterface $e) { $this->view->assign('connectionError', $e); @@ -423,6 +426,7 @@ public function editAction(string $assetSourceIdentifier, string $assetProxyIden 'assetCollections' => $this->assetCollectionRepository->findAll(), 'contentPreview' => $contentPreview, 'assetSource' => $assetSource, + 'assetContainsMaliciousContent' => $this->checkForMaliciousContent($assetProxy), 'canShowVariants' => ($assetProxy instanceof NeosAssetProxy) && ($assetProxy->getAsset() instanceof VariantSupportInterface) ]); } catch (AssetNotFoundExceptionInterface | AssetSourceConnectionExceptionInterface $e) { @@ -1022,4 +1026,25 @@ private function forwardWithConstraints(string $actionName, string $controllerNa } $this->forward($actionName, $controllerName, null, $arguments); } + + private function checkForMaliciousContent(AssetProxyInterface $assetProxy): bool + { + if ($assetProxy->getMediaType() == 'image/svg+xml') { + // @todo: Simplify again when https://github.com/darylldoyle/svg-sanitizer/pull/90 is merged and released. + $previousXmlErrorHandling = libxml_use_internal_errors(true); + $sanitizer = new Sanitizer(); + + $resource = stream_get_contents($assetProxy->getImportStream()); + + $sanitizer->sanitize($resource); + libxml_clear_errors(); + libxml_use_internal_errors($previousXmlErrorHandling); + $issues = $sanitizer->getXmlIssues(); + if ($issues && count($issues) > 0) { + return true; + } + } + + return false; + } } diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php index 6a1c6b924fe..e53cfa0df5a 100644 --- a/Neos.Media.Browser/Classes/Controller/UsageController.php +++ b/Neos.Media.Browser/Classes/Controller/UsageController.php @@ -134,14 +134,14 @@ public function relatedNodesAction(AssetInterface $asset) continue; } - $documentNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypeConstraints: NodeTypeNameFactory::NAME_DOCUMENT)); + $documentNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_DOCUMENT)); // this should actually never happen, too. if (!$documentNode) { $inaccessibleRelations[] = $inaccessibleRelation; continue; } - $siteNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypeConstraints: NodeTypeNameFactory::NAME_SITE)); + $siteNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE)); // this should actually never happen, too. :D if (!$siteNode) { $inaccessibleRelations[] = $inaccessibleRelation; diff --git a/Neos.Media.Browser/Resources/Private/Partials/ContentDefaultPreview.html b/Neos.Media.Browser/Resources/Private/Partials/ContentDefaultPreview.html index f32baac89dd..3f0963cdf42 100644 --- a/Neos.Media.Browser/Resources/Private/Partials/ContentDefaultPreview.html +++ b/Neos.Media.Browser/Resources/Private/Partials/ContentDefaultPreview.html @@ -1,7 +1,14 @@ {namespace m=Neos\Media\ViewHelpers} {namespace neos=Neos\Neos\ViewHelpers}
- - {assetProxy.label} - + + + {assetProxy.label} + + + + {assetProxy.label} + + +
diff --git a/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html b/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html index f40c47e7df1..06ec12fa5d7 100644 --- a/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html +++ b/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html @@ -78,7 +78,19 @@

{neos:backend.translate(id: 'connectionError', package: 'Neos.Media.Browser' {neos:backend.translate(id: 'metadata.filename', package: 'Neos.Media.Browser')} - {assetProxy.filename} + + + + {assetProxy.filename} +
+ {neos:backend.translate(id: 'message.assetContainsMaliciousContent', package: 'Neos.Media.Browser')} +
+
+ + {assetProxy.filename} + +
+ {neos:backend.translate(id: 'metadata.lastModified', package: 'Neos.Media.Browser')} diff --git a/Neos.Media.Browser/Resources/Private/Templates/Asset/Show.html b/Neos.Media.Browser/Resources/Private/Templates/Asset/Show.html index 0375e584638..c74719983bb 100644 --- a/Neos.Media.Browser/Resources/Private/Templates/Asset/Show.html +++ b/Neos.Media.Browser/Resources/Private/Templates/Asset/Show.html @@ -39,7 +39,19 @@ {neos:backend.translate(id: 'metadata.filename', package: 'Neos.Media.Browser')} - {assetProxy.filename} + + + + {assetProxy.filename} +
+ {neos:backend.translate(id: 'message.assetContainsMaliciousContent', package: 'Neos.Media.Browser')} +
+
+ + {assetProxy.filename} + +
+ {neos:backend.translate(id: 'metadata.lastModified', package: 'Neos.Media.Browser')} @@ -85,9 +97,16 @@
- - {assetProxy.label} - + + + {assetProxy.label} + + + + {assetProxy.label} + + +
diff --git a/Neos.Media.Browser/Resources/Private/Translations/en/Main.xlf b/Neos.Media.Browser/Resources/Private/Translations/en/Main.xlf index 07f3354ed02..1dd0ef007a1 100644 --- a/Neos.Media.Browser/Resources/Private/Translations/en/Main.xlf +++ b/Neos.Media.Browser/Resources/Private/Translations/en/Main.xlf @@ -101,6 +101,9 @@ This operation cannot be undone. + + This asset might contain malicious content! + Cancel diff --git a/Neos.Media.Browser/Resources/Private/Translations/es/Main.xlf b/Neos.Media.Browser/Resources/Private/Translations/es/Main.xlf index 1cadd286e74..213a5dfc92c 100644 --- a/Neos.Media.Browser/Resources/Private/Translations/es/Main.xlf +++ b/Neos.Media.Browser/Resources/Private/Translations/es/Main.xlf @@ -570,6 +570,10 @@ Create missing variants Crear las variantes que faltan + + This asset might contain malicious content! + ¡Este activo puede tener contenido malicioso! + diff --git a/Neos.Media.Browser/Resources/Private/Translations/nl/Main.xlf b/Neos.Media.Browser/Resources/Private/Translations/nl/Main.xlf index 3a8056ff555..5999ac0939f 100644 --- a/Neos.Media.Browser/Resources/Private/Translations/nl/Main.xlf +++ b/Neos.Media.Browser/Resources/Private/Translations/nl/Main.xlf @@ -4,431 +4,572 @@ Drag and drop an asset on a collection / tag to add them to it. - Sleep een asset naar een collectie of tag om deze daaraan toe te voegen. + Sleep een asset naar een collectie of tag om deze daaraan toe te voegen. + Keep the filename "{0}" - Het bestand "{0}" behouden + Het bestand "{0}" behouden + Media - Media + Media + This module allows managing of media assets including pictures, videos, audio and documents. - Deze module stelt u in staat media bestanden waaronder afbeeldingen, video's, audio bestanden en documenten te beheren. + Deze module stelt u in staat media bestanden waaronder afbeeldingen, video's, audio bestanden en documenten te beheren. + Name - Naam + Naam + Title - Titel + Titel + Label - Label + Label + Caption - Onderschrift + Onderschrift + Copyright Notice - Copyright melding + Copyright melding + Last modified - Laatst bewerkt + Laatst bewerkt + File size - Bestandsgrootte + Bestandsgrootte + Type - Type + Type + Tags - Tags + Tags + Sort by name - Sorteer op naam + Sorteer op naam + Sort by last modified - Sorteer op laatst gewijzigd + Sorteer op laatst gewijzigd + Sort by - Sorteren op + Sorteren op + Sort direction - Sorteer volgorde + Sorteer volgorde + Ascending - Oplopend + Oplopend + Descending - Aflopend + Aflopend + Sort direction Ascending - Oplopend sorteren + Oplopend sorteren + Sort direction Descending - Aflopend sorteren + Aflopend sorteren + Drag and drop on tag or collection - Slepen en neerzetten op tag of collectie + Slepen en neerzetten op tag of collectie + View - Weergeven + Weergeven + View asset - Bekijk materiaal + Bekijk materiaal + Edit asset - Bestand bewerken + Bestand bewerken + Delete asset - Bestand verwijderen + Bestand verwijderen + Do you really want to delete asset "{0}"? - Weet u zeker dat u bestand "{0}" wilt verwijderen? + Weet u zeker dat u bestand "{0}" wilt verwijderen? + Do you really want to delete collection "{0}"? - Weet u zeker dat u collectie "{0}" wilt verwijderen? + Weet u zeker dat u collectie "{0}" wilt verwijderen? + Do you really want to delete tag "{0}"? - Weet u zeker dat u tag "{0}" wilt verwijderen? + Weet u zeker dat u tag "{0}" wilt verwijderen? + This will delete the asset. - Dit verwijderd het bestand. + Dit verwijderd het bestand. + This will delete the collection, but not the assets that it contains. - Dit verwijderd de collectie, maar niet de bestanden die deze bevat. + Dit verwijderd de collectie, maar niet de bestanden die deze bevat. + This will delete the tag, but not the assets that has it. - Dit verwijderd de tag, maar niet de bestanden die deze gebruikt. + Dit verwijderd de tag, maar niet de bestanden die deze gebruikt. + This operation cannot be undone. - Deze bewerking kan niet ongedaan worden gemaakt. + Deze bewerking kan niet ongedaan worden gemaakt. + Cancel - Annuleren + Annuleren + Replace - Vervang + Vervang + Replace asset resource - Vervang asset bron + Vervang asset bron + Save - Sla op + Sla op + Yes, delete the asset - Ja, verwijder het bestand + Ja, verwijder het bestand + Yes, delete the collection - Ja, verwijder de collectie + Ja, verwijder de collectie + Yes, delete the tag - Ja, verwijder de tag + Ja, verwijder de tag + Edit {0} - Bewerk {0} + Bewerk {0} + Search in assets - Zoek binnen de bestanden + Zoek binnen de bestanden + Search - Zoeken + Zoeken + {0} items - {0} items + {0} items + found matching "{0}" - gevonden overeenkomende "{0}" + gevonden overeenkomende "{0}" + Upload - Upload + Upload + Filter options - Filter opties + Filter opties + Display all asset types - Alle bestandstypen weergeven + Alle bestandstypen weergeven + Only display image assets - Alleen afbeeldingen weergeven + Alleen afbeeldingen weergeven + Only display document assets - Toon enkel documenten + Toon enkel documenten + Only display video assets - Toon enkel videobestanden + Toon enkel videobestanden + Only display audio assets - Toon enkel audiobestanden + Toon enkel audiobestanden + All - Alle + Alle + Images - Afbeeldingen + Afbeeldingen + Documents - Documenten + Documenten + Video - Video + Video + Audio - Audio + Audio + Sort options - Sorteeropties + Sorteeropties + List view - Lijstweergave + Lijstweergave + Thumbnail view - Miniatuurweergave + Miniatuurweergave + Connection error - Verbindingsfout + Verbindingsfout + Media source - Media bron + Media bron + Media sources - Media bronnen + Media bronnen + Collections - Collecties + Collecties + Edit collections - Collecties bewerken + Collecties bewerken + Edit collection - Collectie bewerken + Collectie bewerken + Delete collection - Collectie verwijderen + Collectie verwijderen + Create collection - Collectie aanmaken + Collectie aanmaken + Enter collection title - Voer collectie titel in + Voer collectie titel in + All collections - Alle collecties + Alle collecties + Tags - Tags + Tags + Edit tags - Bewerk tags + Bewerk tags + Edit tag - Bewerk tag + Bewerk tag + Delete tag - Verwijder tag + Verwijder tag + Enter tag label - Voer het label van de tag in + Voer het label van de tag in + Create tag - Maak tag aan + Maak tag aan + All assets - Alle assets + Alle assets + All - Alle + Alle + Untagged assets - Bestanden zonder tag + Bestanden zonder tag + Untagged - Zonder tag + Zonder tag + Max. upload size {0} per file - Max. upload grootte {0} per bestand + Max. upload grootte {0} per bestand + Drop files here - Drop bestanden hier + Drop bestanden hier + or click to upload - of klik om te uploaden + of klik om te uploaden + Choose file - Kies bestand + Kies bestand + No Assets found. - Geen bestanden gevonden. + Geen bestanden gevonden. + Basics - Basis + Basis + Delete - Verwijderen + Verwijderen + Click to delete - Klik om te verwijderen + Klik om te verwijderen + Save - Sla op + Sla op + Metadata - Metadata + Metadata + Filename - Bestandsnaam + Bestandsnaam + Last modified (resource) - Laatst bewerkt (resource) + Laatst bewerkt (resource) + File size - Bestandsgrootte + Bestandsgrootte + Dimensions - Dimensies + Dimensies + Type - Type + Type + Identifier - Identifier + Identifier + Title - Titel + Titel + Caption - Onderschrift + Onderschrift + Copyright Notice - Copyright melding + Copyright melding + Preview - Voorbeeld + Voorbeeld + Download - Downloaden + Downloaden + Next - Volgende + Volgende + Previous - Vorige + Vorige + Cannot upload the file - Kan het bestand niet uploaden + Kan het bestand niet uploaden + No file selected - Geen bestand geselecteerd + Geen bestand geselecteerd + The file size of {0} exceeds the allowed limit of {1} - De bestandsgrootte van {0} groter is dan de toegestane limiet van {1} + De bestandsgrootte van {0} groter is dan de toegestane limiet van {1} + for the file - voor het bestand + voor het bestand + Only some of the files were successfully uploaded. Refresh the page to see the those. - Slechts enkele van de bestanden zijn geüpload. Vernieuw de pagina om die te zien. + Slechts enkele van de bestanden zijn geüpload. Vernieuw de pagina om die te zien. + Tagging the asset failed. - Taggen van bestand is mislukt. + Taggen van bestand is mislukt. + Adding the asset to the collection failed. - Het toevoegen van het bestand aan de collectie is niet gelukt. + Het toevoegen van het bestand aan de collectie is niet gelukt. + Creating - Wordt aangemaakt + Wordt aangemaakt + Asset could not be deleted, because there are still Nodes using it - Bestand kon niet worden verwijderd, omdat het nog door nodes wordt gebruikt + Bestand kon niet worden verwijderd, omdat het nog door nodes wordt gebruikt + {0} usages - {0} keer toegepast - + {0} keer toegepast {0} usages - {0} keer toegepast - + {0} keer toegepast References to "{asset}" - Referenties naar "{asset}" + Referenties naar "{asset}" + Replace "{filename}" - Vervang "{filename}" + Vervang "{filename}" + You can replace this asset by uploading a new file. Once replaced, the new asset will be used on all places where the asset is used on your website. - Je kunt dit bestand vervangen door een nieuw bestand te uploaden. Eenmaal vervangen, zal het nieuwe bestand worden gebruikt op alle plaatsen waar de asset op uw website wordt gebruikt. + Je kunt dit bestand vervangen door een nieuw bestand te uploaden. Eenmaal vervangen, zal het nieuwe bestand worden gebruikt op alle plaatsen waar de asset op uw website wordt gebruikt. + Note - Notitie + Notitie + This operation will replace the asset in all published or unpublished workspaces, including the live website. - Deze operatie vervangt de asset in alle gepubliceerde of ongepubliceerde werkruimten, inclusief de live website. + Deze operatie vervangt de asset in alle gepubliceerde of ongepubliceerde werkruimten, inclusief de live website. + Choose a new file - Kies een nieuw bestand + Kies een nieuw bestand + Currently the asset is used {usageCount} times. - Momenteel wordt de asset {usageCount} keer gebruikt. + Momenteel wordt de asset {usageCount} keer gebruikt. + Show all usages - Toon alle toepassingen + Toon alle toepassingen + Currently the asset is not in used anywhere on the website. - Momenteel wordt de asset nergens op de website gebruikt. + Momenteel wordt de asset nergens op de website gebruikt. + Preview current file - Voorbeeld huidige bestand + Voorbeeld huidige bestand + Could not replace asset - Kon het bestand niet vervangen + Kon het bestand niet vervangen + Asset "{0}" has been replaced. - Bestand "{0}" is vervangen. + Bestand "{0}" is vervangen. + Asset "{0}" has been updated. - Bestand "{0}" is aangepast. + Bestand "{0}" is aangepast. + Asset "{0}" has been added. - Bestand "{0}" is toegevoegd. + Bestand "{0}" is toegevoegd. + Asset "{0}" has been deleted. - Bestand "{0}" is verwijderd. + Bestand "{0}" is verwijderd. + Asset could not be deleted. - Het bestand kon niet worden verwijderd. + Het bestand kon niet worden verwijderd. + Tag "{0}" already exists and was added to collection. - Tag "{0}" bestond al en is toegevoegd aan de collectie. + Tag "{0}" bestond al en is toegevoegd aan de collectie. + Tag "{0}" has been created. - Tag "{0}" is aangemaakt. + Tag "{0}" is aangemaakt. + Tag "{0}" has been updated. - Tag "{0}" is aangepast. + Tag "{0}" is aangepast. + Tag "{0}" has been deleted. - Tag "{0}" is verwijderd. + Tag "{0}" is verwijderd. + Collection "{0}" has been created. - Collectie "{0}" is aangemaakt. + Collectie "{0}" is aangemaakt. + Collection "{0}" has been updated. - Collectie "{0}" is aangepast. + Collectie "{0}" is aangepast. + Collection "{0}" has been deleted. - Collectie "{0}" is verwijderd. + Collectie "{0}" is verwijderd. + Generate redirects from original file url to the new url - Genereer een redirect van de originele locatie van het bestand naar de nieuwe locatie + Genereer een redirect van de originele locatie van het bestand naar de nieuwe locatie + 'Resources of type "{0}" can only be replaced by a similar resource. Got type "{1}"' - 'Bronnen van type "{0}' kunnen alleen worden vervangen door een vergelijkbare bron. Kreeg type "{1}"' + 'Bronnen van type "{0}' kunnen alleen worden vervangen door een vergelijkbare bron. Kreeg type "{1}"' + No access to workspace "{0}" - Geen toegang tot werkplaats "{0}" + Geen toegang tot werkplaats "{0}" + No document node found for this node - Geen documentnode gevonden voor deze node + Geen documentnode gevonden voor deze node + + + Create missing variants + Maak ontbrekende varianten aan + diff --git a/Neos.Media.Browser/composer.json b/Neos.Media.Browser/composer.json index 85edd1fe87f..27ef651e188 100644 --- a/Neos.Media.Browser/composer.json +++ b/Neos.Media.Browser/composer.json @@ -12,6 +12,7 @@ ], "require": { "php": "^8.2", + "ext-libxml": "*", "neos/media": "self.version", "neos/contentrepository-core": "self.version", "neos/neos": "self.version", @@ -22,7 +23,8 @@ "neos/utility-mediatypes": "*", "neos/error-messages": "*", "doctrine/common": "^2.7 || ^3.0", - "doctrine/orm": "^2.6" + "doctrine/orm": "^2.6", + "enshrined/svg-sanitize": "^0.16.0" }, "autoload": { "psr-4": { diff --git a/Neos.Media/Classes/Domain/Repository/AssetRepository.php b/Neos.Media/Classes/Domain/Repository/AssetRepository.php index d8831606d18..9e2e9347acb 100644 --- a/Neos.Media/Classes/Domain/Repository/AssetRepository.php +++ b/Neos.Media/Classes/Domain/Repository/AssetRepository.php @@ -89,7 +89,7 @@ public function findBySearchTermOrTags($searchTerm, array $tags = [], AssetColle $constraints[] = $query->contains('tags', $tag); } $query->matching($query->logicalOr($constraints)); - $this->addAssetVariantFilterClause($query); + $this->addAssetVariantToQueryConstraints($query); $this->addAssetCollectionToQueryConstraints($query, $assetCollection); return $query->execute(); } @@ -106,7 +106,7 @@ public function findByTag(Tag $tag, AssetCollection $assetCollection = null): Qu { $query = $this->createQuery(); $query->matching($query->contains('tags', $tag)); - $this->addAssetVariantFilterClause($query); + $this->addAssetVariantToQueryConstraints($query); $this->addAssetCollectionToQueryConstraints($query, $assetCollection); return $query->execute(); } @@ -149,7 +149,7 @@ public function countByTag(Tag $tag, AssetCollection $assetCollection = null): i public function findAll(AssetCollection $assetCollection = null): QueryResultInterface { $query = $this->createQuery(); - $this->addAssetVariantFilterClause($query); + $this->addAssetVariantToQueryConstraints($query); $this->addAssetCollectionToQueryConstraints($query, $assetCollection); return $query->execute(); } @@ -190,7 +190,7 @@ public function findUntagged(AssetCollection $assetCollection = null): QueryResu { $query = $this->createQuery(); $query->matching($query->isEmpty('tags')); - $this->addAssetVariantFilterClause($query); + $this->addAssetVariantToQueryConstraints($query); $this->addAssetCollectionToQueryConstraints($query, $assetCollection); return $query->execute(); } @@ -231,7 +231,7 @@ public function countUntagged(AssetCollection $assetCollection = null): int public function findByAssetCollection(AssetCollection $assetCollection): QueryResultInterface { $query = $this->createQuery(); - $this->addAssetVariantFilterClause($query); + $this->addAssetVariantToQueryConstraints($query); $this->addAssetCollectionToQueryConstraints($query, $assetCollection); return $query->execute(); } @@ -280,14 +280,16 @@ protected function addAssetCollectionToQueryConstraints(QueryInterface $query, A * @param Query $query * @return void */ - protected function addAssetVariantFilterClause(Query $query): void + protected function addAssetVariantToQueryConstraints(QueryInterface $query): void { - $queryBuilder = $query->getQueryBuilder(); - + $variantsConstraints = []; $variantClassNames = $this->reflectionService->getAllImplementationClassNamesForInterface(AssetVariantInterface::class); foreach ($variantClassNames as $variantClassName) { - $queryBuilder->andWhere('e NOT INSTANCE OF ' . $variantClassName); + $variantsConstraints[] = 'e NOT INSTANCE OF ' . $variantClassName; } + + $constraints = $query->getConstraint(); + $query->matching($query->logicalAnd([$constraints, $query->logicalAnd($variantsConstraints)])); } /** @@ -354,7 +356,7 @@ public function findAllIterator(): IterableResult { /** @var Query $query */ $query = $this->createQuery(); - $this->addAssetVariantFilterClause($query); + $this->addAssetVariantToQueryConstraints($query); return $query->getQueryBuilder()->getQuery()->iterate(); } diff --git a/Neos.Media/Classes/Domain/Service/AssetService.php b/Neos.Media/Classes/Domain/Service/AssetService.php index 0df8f14449c..ae947d94d78 100644 --- a/Neos.Media/Classes/Domain/Service/AssetService.php +++ b/Neos.Media/Classes/Domain/Service/AssetService.php @@ -323,7 +323,9 @@ public function replaceAssetResource(AssetInterface $asset, PersistentResource $ $variant->refresh(); foreach ($variant->getAdjustments() as $adjustment) { if (method_exists($adjustment, 'refit') && $this->imageService->getImageSize($originalAssetResource) !== $this->imageService->getImageSize($resource)) { - $adjustment->refit($asset); + if ($asset instanceof ImageInterface && $asset->getWidth() !== null && $asset->getHeight() !== null) { + $adjustment->refit($asset); + } } } $this->getRepository($variant)->update($variant); diff --git a/Neos.Media/Tests/Functional/Domain/Repository/AssetRepositoryTest.php b/Neos.Media/Tests/Functional/Domain/Repository/AssetRepositoryTest.php index 7b8ff4ab728..7d3cd85a29a 100644 --- a/Neos.Media/Tests/Functional/Domain/Repository/AssetRepositoryTest.php +++ b/Neos.Media/Tests/Functional/Domain/Repository/AssetRepositoryTest.php @@ -10,7 +10,13 @@ * information, please view the LICENSE file which was distributed with this * source code. */ + +use Doctrine\Common\Collections\ArrayCollection; use Neos\Flow\Persistence\Doctrine\PersistenceManager; +use Neos\Media\Domain\Model\AssetCollection; +use Neos\Media\Domain\Model\Image; +use Neos\Media\Domain\Model\ImageVariant; +use Neos\Media\Domain\Repository\AssetCollectionRepository; use Neos\Utility\Files; use Neos\Media\Domain\Model\Asset; use Neos\Media\Domain\Model\Tag; @@ -34,6 +40,11 @@ class AssetRepositoryTest extends AbstractTest */ protected $assetRepository; + /** + * @var AssetCollectionRepository + */ + protected $assetCollectionRepository; + /** * @var TagRepository */ @@ -52,6 +63,7 @@ public function setUp(): void $this->prepareResourceManager(); $this->assetRepository = $this->objectManager->get(AssetRepository::class); + $this->assetCollectionRepository = $this->objectManager->get(AssetCollectionRepository::class); $this->tagRepository = $this->objectManager->get(TagRepository::class); } @@ -147,4 +159,91 @@ public function findBySearchTermAndTagsReturnsFilteredResult() $asset->getResource()->getSha1(); } } + + /** + * @test + */ + public function testAddAssetVariantFilterClauseWithoutAssetCollection() + { + $resource1 = $this->resourceManager->importResource(__DIR__ . '/../../Fixtures/Resources/417px-Mihaly_Csikszentmihalyi.jpg'); + $resource2 = $this->resourceManager->importResource(__DIR__ . '/../../Fixtures/Resources/640px-Goodworkteam.jpg'); + + $image1 = new Image($resource1); + $image1->setTitle('asset for homepage'); + $this->assetRepository->add($image1); + + $imageVariant1 = new ImageVariant($image1); + $image1->addVariant($imageVariant1); + + $image2 = new Image($resource2); + $image2->setTitle('asset for homepage'); + $this->assetRepository->add($image2); + + $imageVariant2 = new ImageVariant($image2); + $image2->addVariant($imageVariant2); + + $this->persistenceManager->persistAll(); + $this->persistenceManager->clearState(); + + $assets = $this->assetRepository->findAll(); + + self::assertCount(2, $assets); + foreach ($assets as $asset) { + self::assertInstanceOf(Image::class, $asset); + self::assertNotInstanceOf(ImageVariant::class, $asset); + } + + // This is necessary to initialize all resource instances before the tables are deleted + foreach ($this->assetRepository->findAll() as $asset) { + $asset->getResource()->getSha1(); + } + } + + /** + * @test + */ + public function testAddAssetVariantFilterClauseWithAssetCollection() + { + $resource1 = $this->resourceManager->importResource(__DIR__ . '/../../Fixtures/Resources/417px-Mihaly_Csikszentmihalyi.jpg'); + $resource2 = $this->resourceManager->importResource(__DIR__ . '/../../Fixtures/Resources/640px-Goodworkteam.jpg'); + + $assetCollection1 = new AssetCollection('test-1'); + $this->assetCollectionRepository->add($assetCollection1); + + $collections1 = new ArrayCollection(); + $collections1->add($assetCollection1); + + $image1 = new Image($resource1); + $image1->setTitle('asset for homepage'); + $image1->setAssetCollections($collections1); + $assetCollection1->addAsset($image1); + + $imageVariant1 = new ImageVariant($image1); + $image1->addVariant($imageVariant1); + + $assetCollection2 = new AssetCollection('test-2'); + $this->assetCollectionRepository->add($assetCollection2); + + $collections2 = new ArrayCollection(); + $collections2->add($assetCollection2); + + $image2 = new Image($resource2); + $image2->setTitle('asset for homepage'); + $image2->setAssetCollections($collections2); + $assetCollection2->addAsset($image2); + + $this->persistenceManager->persistAll(); + $this->persistenceManager->clearState(); + + $assets = $this->assetRepository->findAll($assetCollection1); + self::assertCount(1, $assets); + self::assertInstanceOf(Image::class, $assets->getFirst()); + self::assertNotInstanceOf(ImageVariant::class, $assets->getFirst()); + self::assertNotInstanceOf(ImageVariant::class, $assets->getFirst()); + + // This is necessary to initialize all resource instances before the tables are deleted + foreach ($this->assetRepository->findAll() as $asset) { + $asset->getResource()->getSha1(); + } + } } diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php index 7e5bf890b43..517fd56637a 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php @@ -9,7 +9,11 @@ use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\ForwardCompatibility\Result; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Type; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; +use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\Neos\AssetUsage\Dto\AssetUsageNodeAddress; @@ -42,54 +46,32 @@ public function setup(): void throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - $schemaDiff = (new Comparator())->compare($schemaManager->createSchema(), self::databaseSchema($this->tableNamePrefix)); + $schema = DbalSchemaFactory::createSchemaWithTables($schemaManager, [self::databaseSchema($this->tableNamePrefix)]); + $schemaDiff = (new Comparator())->compare($schemaManager->createSchema(), $schema); foreach ($schemaDiff->toSaveSql($this->dbal->getDatabasePlatform()) as $statement) { $this->dbal->executeStatement($statement); } } - private static function databaseSchema(string $tablePrefix): Schema + private static function databaseSchema(string $tablePrefix): Table { - $schema = new Schema(); - - $table = $schema->createTable($tablePrefix); - $table->addColumn('assetid', Types::STRING) - ->setLength(40) - ->setNotnull(true) - ->setDefault(''); - $table->addColumn('originalassetid', Types::STRING) - ->setLength(40) - ->setNotnull(false) - ->setDefault(null); - $table->addColumn('contentstreamid', Types::STRING) - ->setLength(40) - ->setNotnull(true) - ->setDefault(''); - $table->addColumn('nodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(true) - ->setDefault(''); - $table->addColumn('origindimensionspacepoint', Types::TEXT) - ->setNotnull(false); - $table->addColumn('origindimensionspacepointhash', Types::STRING) - // this is an MD5(...), so 32 chars long - ->setLength(32) - ->setNotnull(true) - ->setDefault(''); - $table->addColumn('propertyname', Types::STRING) - ->setLength(255) - ->setNotnull(true) - ->setDefault(''); + $table = new Table($tablePrefix, [ + (new Column('assetid', Type::getType(Types::STRING)))->setLength(40)->setNotnull(true)->setDefault(''), + (new Column('originalassetid', Type::getType(Types::STRING)))->setLength(40)->setNotnull(false)->setDefault(null), + DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotNull(true), + DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotNull(true), + DbalSchemaFactory::columnForDimensionSpacePoint('origindimensionspacepoint')->setNotNull(false), + DbalSchemaFactory::columnForDimensionSpacePoint('origindimensionspacepointhash')->setNotNull(true), + (new Column('propertyname', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setDefault('') + ]); - $table + return $table ->addUniqueIndex(['assetid', 'originalassetid', 'contentstreamid', 'nodeaggregateid', 'origindimensionspacepointhash', 'propertyname'], 'assetperproperty') ->addIndex(['assetid']) ->addIndex(['originalassetid']) ->addIndex(['contentstreamid']) ->addIndex(['nodeaggregateid']) ->addIndex(['origindimensionspacepointhash']); - - return $schema; } public function findUsages(AssetUsageFilter $filter): AssetUsages diff --git a/Neos.Neos/Classes/Command/CrCommandController.php b/Neos.Neos/Classes/Command/CrCommandController.php index 0a1a5e194fe..fdf13fa8db9 100644 --- a/Neos.Neos/Classes/Command/CrCommandController.php +++ b/Neos.Neos/Classes/Command/CrCommandController.php @@ -76,7 +76,7 @@ public function exportCommand(string $path, string $contentRepository = 'default /** * Import the events from the path into the specified content repository * - * @param string $path The path for storing the result + * @param string $path The path of the stored events like resource://Neos.Demo/Private/Content * @param string $contentRepository The content repository identifier * @param bool $verbose If set, all notices will be rendered * @throws \Exception diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php index 5adfeaa32a2..123b33cb39f 100644 --- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php +++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php @@ -18,6 +18,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphWithRuntimeCaches\ContentSubgraphWithRuntimeCaches; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphWithRuntimeCaches\InMemoryCache; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSubtreeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; @@ -34,7 +35,6 @@ use Neos\Flow\Session\SessionInterface; use Neos\Flow\Utility\Now; use Neos\Neos\Domain\Model\RenderingMode; -use Neos\Neos\Domain\Service\NodeSiteResolvingService; use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Neos\Neos\Domain\Service\RenderingModeService; use Neos\Neos\FrontendRouting\Exception\InvalidShortcutException; @@ -103,15 +103,12 @@ class NodeController extends ActionController */ protected $view; - /** - * @Flow\Inject - * @var NodeSiteResolvingService - */ - protected $nodeSiteResolvingService; - #[Flow\Inject] protected RenderingModeService $renderingModeService; + #[Flow\InjectConfiguration(path: "frontend.shortcutRedirectHttpStatusCode", package: "Neos.Neos")] + protected int $shortcutRedirectHttpStatusCode; + /** * @param string $node Legacy name for backwards compatibility of route components * @throws NodeNotFoundException @@ -145,10 +142,7 @@ public function previewAction(string $node): void $visibilityConstraints ); - $site = $this->nodeSiteResolvingService->findSiteNodeForNodeAddress( - $nodeAddress, - $siteDetectionResult->contentRepositoryId - ); + $site = $subgraph->findClosestNode($nodeAddress->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE)); if ($site === null) { throw new NodeNotFoundException("TODO: SITE NOT FOUND; should not happen (for address " . $nodeAddress); } @@ -214,10 +208,7 @@ public function showAction(string $node): void VisibilityConstraints::frontend() ); - $site = $this->nodeSiteResolvingService->findSiteNodeForNodeAddress( - $nodeAddress, - $siteDetectionResult->contentRepositoryId - ); + $site = $subgraph->findClosestNode($nodeAddress->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE)); if ($site === null) { throw new NodeNotFoundException("TODO: SITE NOT FOUND; should not happen (for address " . $nodeAddress); } @@ -311,7 +302,8 @@ protected function handleShortcutNode(NodeAddress $nodeAddress, ContentRepositor } else { $resolvedUri = $resolvedTarget; } - $this->redirectToUri($resolvedUri); + + $this->redirectToUri($resolvedUri, statusCode: $this->shortcutRedirectHttpStatusCode); } private function fillCacheWithContentNodes( diff --git a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php index ec474622f5c..2e0ce705dd3 100644 --- a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php +++ b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php @@ -936,8 +936,8 @@ protected function renderContentChanges( 'diff' => $diffArray ]; } - // The && in belows condition is on purpose as creating a thumbnail for comparison only works - // if actually BOTH are ImageInterface (or NULL). + // The && in belows condition is on purpose as creating a thumbnail for comparison only works + // if actually BOTH are ImageInterface (or NULL). } elseif ( ($originalPropertyValue instanceof ImageInterface || $originalPropertyValue === null) && ($changedPropertyValue instanceof ImageInterface || $changedPropertyValue === null) diff --git a/Neos.Neos/Classes/Domain/Model/SiteConfiguration.php b/Neos.Neos/Classes/Domain/Model/SiteConfiguration.php index 2203d4b04f0..6b39520704f 100644 --- a/Neos.Neos/Classes/Domain/Model/SiteConfiguration.php +++ b/Neos.Neos/Classes/Domain/Model/SiteConfiguration.php @@ -13,6 +13,21 @@ */ final class SiteConfiguration { + /** + * @param ContentRepositoryId $contentRepositoryId + * @param string $contentDimensionResolverFactoryClassName + * @param array $contentDimensionResolverOptions + * @param DimensionSpacePoint $defaultDimensionSpacePoint + */ + private function __construct( + public readonly ContentRepositoryId $contentRepositoryId, + public readonly string $contentDimensionResolverFactoryClassName, + public readonly array $contentDimensionResolverOptions, + public readonly DimensionSpacePoint $defaultDimensionSpacePoint, + public readonly string $uriPathSuffix, + ) { + } + /** * @param array $configuration * @return static @@ -43,19 +58,4 @@ public static function fromArray(array $configuration): self $uriPathSuffix, ); } - - /** - * @param ContentRepositoryId $contentRepositoryId - * @param string $contentDimensionResolverFactoryClassName - * @param array $contentDimensionResolverOptions - * @param DimensionSpacePoint $defaultDimensionSpacePoint - */ - private function __construct( - public readonly ContentRepositoryId $contentRepositoryId, - public readonly string $contentDimensionResolverFactoryClassName, - public readonly array $contentDimensionResolverOptions, - public readonly DimensionSpacePoint $defaultDimensionSpacePoint, - public readonly string $uriPathSuffix, - ) { - } } diff --git a/Neos.Neos/Classes/Domain/Service/NodeSiteResolvingService.php b/Neos.Neos/Classes/Domain/Service/NodeSiteResolvingService.php deleted file mode 100644 index b9314e7d6dc..00000000000 --- a/Neos.Neos/Classes/Domain/Service/NodeSiteResolvingService.php +++ /dev/null @@ -1,55 +0,0 @@ -contentRepositoryRegistry->get( - $contentRepositoryId - ); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, - $nodeAddress->dimensionSpacePoint, - $nodeAddress->isInLiveWorkspace() - ? VisibilityConstraints::frontend() - : VisibilityConstraints::withoutRestrictions() - ); - - $node = $subgraph->findNodeById($nodeAddress->nodeAggregateId); - if (!$node) { - return null; - } - $siteNode = $subgraph->findClosestNode($node->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE)); - - return $siteNode; - } -} diff --git a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php index a93df26f963..548c392160b 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php +++ b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php @@ -17,6 +17,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; @@ -75,9 +76,9 @@ public function findSiteNodeBySite( ); $rootNode = $rootNodeAggregate->getNodeByCoveredDimensionSpacePoint($dimensionSpacePoint); - $siteNode = $subgraph->findChildNodeConnectedThroughEdgeName( - $rootNode->nodeAggregateId, - $site->getNodeName()->toNodeName() + $siteNode = $subgraph->findNodeByPath( + $site->getNodeName()->toNodeName(), + $rootNode->nodeAggregateId ); if (!$siteNode) { diff --git a/Neos.Neos/Classes/Domain/Service/UserService.php b/Neos.Neos/Classes/Domain/Service/UserService.php index 6edd9b5332f..bab470d18b0 100644 --- a/Neos.Neos/Classes/Domain/Service/UserService.php +++ b/Neos.Neos/Classes/Domain/Service/UserService.php @@ -685,7 +685,7 @@ public function deactivateUser(User $user): void */ public function currentUserCanPublishToWorkspace(Workspace $workspace): bool { - if ($workspace->workspaceName->isLive()) { + if ($workspace->isPublicWorkspace()) { return $this->securityContext->hasRole('Neos.Neos:LivePublisher'); } @@ -709,7 +709,7 @@ public function currentUserCanPublishToWorkspace(Workspace $workspace): bool */ public function currentUserCanReadWorkspace(Workspace $workspace): bool { - if ($workspace->workspaceName->isLive() || $workspace->workspaceOwner === null) { + if ($workspace->isPublicWorkspace()) { return true; } diff --git a/Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php b/Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php index 264e381b84b..7e80a7633e0 100644 --- a/Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php +++ b/Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php @@ -15,7 +15,6 @@ namespace Neos\Neos\FrontendRouting; use GuzzleHttp\Psr7\Uri; -use Neos\Neos\FrontendRouting\NodeAddress; use Neos\Flow\Http\Exception as HttpException; use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\Exception\NoMatchingRouteException; @@ -60,13 +59,14 @@ public static function fromUriBuilder(UriBuilder $uriBuilder): static * * @param NodeAddress $nodeAddress * @return UriInterface - * @throws NoMatchingRouteException | MissingActionNameException | HttpException + * @throws NoMatchingRouteException if the node address does not exist */ public function uriFor(NodeAddress $nodeAddress): UriInterface { if (!$nodeAddress->isInLiveWorkspace()) { return $this->previewUriFor($nodeAddress); } + /** @noinspection PhpUnhandledExceptionInspection */ return new Uri($this->uriBuilder->uriFor('show', ['node' => $nodeAddress], 'Frontend\Node', 'Neos.Neos')); } @@ -76,7 +76,7 @@ public function uriFor(NodeAddress $nodeAddress): UriInterface * * @param NodeAddress $nodeAddress * @return UriInterface - * @throws NoMatchingRouteException | MissingActionNameException | HttpException + * @throws NoMatchingRouteException if the node address does not exist */ public function previewUriFor(NodeAddress $nodeAddress): UriInterface { diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php index 35c8b22564f..b0b9fa03f4f 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php @@ -86,8 +86,7 @@ private function setupTables(): SetupResult if (!$schemaManager instanceof AbstractSchemaManager) { throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - - $schema = (new DocumentUriPathSchemaBuilder($this->tableNamePrefix))->buildSchema(); + $schema = (new DocumentUriPathSchemaBuilder($this->tableNamePrefix))->buildSchema($schemaManager); $schemaDiff = (new Comparator())->compare($schemaManager->createSchema(), $schema); foreach ($schemaDiff->toSaveSql($connection->getDatabasePlatform()) as $statement) { diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathSchemaBuilder.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathSchemaBuilder.php index 66b152af2fc..9c896efcfaf 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathSchemaBuilder.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathSchemaBuilder.php @@ -2,76 +2,52 @@ namespace Neos\Neos\FrontendRouting\Projection; +use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\SchemaConfig; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; +use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; class DocumentUriPathSchemaBuilder { + private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; + public function __construct( private readonly string $tableNamePrefix, ) { } - public function buildSchema(): Schema + public function buildSchema(AbstractSchemaManager $schemaManager): Schema { - $schema = new Schema(); - - $this->createUriTable($schema); - $this->createLiveContentStreamsTable($schema); + $schema = DbalSchemaFactory::createSchemaWithTables($schemaManager, [ + $this->createUriTable(), + $this->createLiveContentStreamsTable() + ]); return $schema; } - private function createUriTable(Schema $schema): void + private function createUriTable(): Table { + $table = new Table($this->tableNamePrefix . '_uri', [ + DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotNull(true), + (new Column('uripath', Type::getType(Types::STRING)))->setLength(4000)->setDefault('')->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash')->setNotNull(true), + (new Column('disabled', Type::getType(Types::INTEGER)))->setLength(4)->setUnsigned(true)->setDefault(0)->setNotnull(true), + (new Column('nodeaggregateidpath', Type::getType(Types::STRING)))->setLength(4000)->setDefault('')->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + (new Column('sitenodename', Type::getType(Types::STRING)))->setLength(255)->setDefault('')->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotNull(true), + DbalSchemaFactory::columnForNodeAggregateId('parentnodeaggregateid')->setNotNull(false), + DbalSchemaFactory::columnForNodeAggregateId('precedingnodeaggregateid')->setNotNull(false), + DbalSchemaFactory::columnForNodeAggregateId('succeedingnodeaggregateid')->setNotNull(false), + (new Column('shortcuttarget', Type::getType(Types::STRING)))->setLength(1000)->setNotnull(false)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + DbalSchemaFactory::columnForNodeTypeName('nodetypename') + ]); - $table = $schema->createTable($this->tableNamePrefix . '_uri'); - $table->addColumn('nodeaggregateid', Types::STRING) - ->setLength(64) - ->setDefault('') - ->setNotnull(true); - $table->addColumn('uripath', Types::STRING) - ->setLength(4000) - ->setDefault('') - ->setNotnull(true); - $table->addColumn('dimensionspacepointhash', Types::STRING) - ->setLength(255) - ->setDefault('') - ->setNotnull(true); - $table->addColumn('disabled', Types::INTEGER) - ->setLength(4) - ->setUnsigned(true) - ->setDefault(0) - ->setNotnull(true); - $table->addColumn('nodeaggregateidpath', Types::STRING) - ->setLength(4000) - ->setDefault('') - ->setNotnull(true); - $table->addColumn('sitenodename', Types::STRING) - ->setLength(255) - ->setDefault('') - ->setNotnull(true); - $table->addColumn('origindimensionspacepointhash', Types::STRING) - ->setLength(255) - ->setDefault('') - ->setNotnull(true); - $table->addColumn('parentnodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(false); - $table->addColumn('precedingnodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(false); - $table->addColumn('succeedingnodeaggregateid', Types::STRING) - ->setLength(64) - ->setNotnull(false); - $table->addColumn('shortcuttarget', Types::STRING) - ->setLength(1000) - ->setNotnull(false); - $table->addColumn('nodetypename', Types::STRING) - ->setLength(255) - ->setNotnull(true); - - $table + return $table ->addUniqueIndex(['nodeaggregateid', 'dimensionspacepointhash'], 'variant') ->addIndex([ 'parentnodeaggregateid', @@ -81,17 +57,12 @@ private function createUriTable(Schema $schema): void ->addIndex(['sitenodename', 'uripath'], null, [], ['lengths' => [null,100]]); } - private function createLiveContentStreamsTable(Schema $schema): void + private function createLiveContentStreamsTable(): Table { - $table = $schema->createTable($this->tableNamePrefix . '_livecontentstreams'); - $table->addColumn('contentstreamid', Types::STRING) - ->setLength(40) - ->setDefault('') - ->setNotnull(true); - $table->addColumn('workspacename', Types::STRING) - ->setLength(255) - ->setDefault('') - ->setNotnull(true); - $table->setPrimaryKey(['contentstreamid']); + $table = new Table($this->tableNamePrefix . '_livecontentstreams', [ + DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotNull(true), + (new Column('workspacename', Type::getType(Types::STRING)))->setLength(255)->setDefault('')->setNotnull(true) + ]); + return $table->setPrimaryKey(['contentstreamid']); } } diff --git a/Neos.Neos/Classes/Fusion/ContentElementEditableImplementation.php b/Neos.Neos/Classes/Fusion/ContentElementEditableImplementation.php index 6ddcdc300ee..9e24293f650 100644 --- a/Neos.Neos/Classes/Fusion/ContentElementEditableImplementation.php +++ b/Neos.Neos/Classes/Fusion/ContentElementEditableImplementation.php @@ -18,6 +18,7 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Security\Authorization\PrivilegeManagerInterface; use Neos\Fusion\FusionObjects\AbstractFusionObject; +use Neos\Neos\Domain\Model\RenderingMode; use Neos\Neos\Service\ContentElementEditableService; /** @@ -56,18 +57,24 @@ public function evaluate() { $content = $this->getValue(); + $renderingMode = $this->runtime->fusionGlobals->get('renderingMode'); + assert($renderingMode instanceof RenderingMode); + if (!$renderingMode->isEdit) { + return $content; + } + $node = $this->fusionValue('node'); if (!$node instanceof Node) { return $content; } - /** @var string $property */ - $property = $this->fusionValue('property'); - if (!$this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess')) { return $content; } + /** @var string $property */ + $property = $this->fusionValue('property'); + return $this->contentElementEditableService->wrapContentProperty($node, $property, $content); } } diff --git a/Neos.Neos/Classes/Fusion/ContentElementWrappingImplementation.php b/Neos.Neos/Classes/Fusion/ContentElementWrappingImplementation.php index ea8ca0eb6a2..6e2b85fbc85 100644 --- a/Neos.Neos/Classes/Fusion/ContentElementWrappingImplementation.php +++ b/Neos.Neos/Classes/Fusion/ContentElementWrappingImplementation.php @@ -18,6 +18,7 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Security\Authorization\PrivilegeManagerInterface; use Neos\Fusion\FusionObjects\AbstractFusionObject; +use Neos\Neos\Domain\Model\RenderingMode; use Neos\Neos\Service\ContentElementWrappingService; /** @@ -66,6 +67,12 @@ public function evaluate() { $content = $this->getValue(); + $renderingMode = $this->runtime->fusionGlobals->get('renderingMode'); + assert($renderingMode instanceof RenderingMode); + if (!$renderingMode->isEdit) { + return $content; + } + $node = $this->fusionValue('node'); if (!$node instanceof Node) { return $content; diff --git a/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php b/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php index 2ac84a6e28e..151bf487fc4 100644 --- a/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php +++ b/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php @@ -15,6 +15,9 @@ namespace Neos\Neos\Fusion; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\Flow\Log\Utility\LogEnvironment; +use Neos\Flow\Mvc\Exception\NoMatchingRouteException; +use Neos\Neos\Domain\Model\RenderingMode; use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; @@ -26,6 +29,7 @@ use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\Domain\Exception as NeosException; +use Psr\Log\LoggerInterface; /** * A Fusion Object that converts link references in the format "://" to proper URIs @@ -81,6 +85,12 @@ class ConvertUrisImplementation extends AbstractFusionObject */ protected $contentRepositoryRegistry; + /** + * @Flow\Inject + * @var LoggerInterface + */ + protected $systemLogger; + /** * Convert URIs matching a supported scheme with generated URIs * @@ -114,16 +124,18 @@ public function evaluate() ), 1382624087); } + $renderingMode = $this->runtime->fusionGlobals->get('renderingMode'); + assert($renderingMode instanceof RenderingMode); + if ($renderingMode->isEdit && $this->fusionValue('forceConversion') !== true) { + return $text; + } + $contentRepository = $this->contentRepositoryRegistry->get( $node->subgraphIdentity->contentRepositoryId ); $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromNode($node); - if (!$nodeAddress->isInLiveWorkspace() && !($this->fusionValue('forceConversion'))) { - return $text; - } - $unresolvedUris = []; $absolute = $this->fusionValue('absolute'); @@ -139,10 +151,11 @@ function (array $matches) use (&$unresolvedUris, $absolute, $nodeAddress) { $uriBuilder = new UriBuilder(); $uriBuilder->setRequest($this->runtime->getControllerContext()->getRequest()); $uriBuilder->setCreateAbsoluteUri($absolute); - - // TODO: multi-site .... - // -> different object than NodeAddress which also contains the CR Identifier. - $resolvedUri = (string)NodeUriBuilder::fromUriBuilder($uriBuilder)->uriFor($nodeAddress); + try { + $resolvedUri = (string)NodeUriBuilder::fromUriBuilder($uriBuilder)->uriFor($nodeAddress); + } catch (NoMatchingRouteException) { + $this->systemLogger->info(sprintf('Could not resolve "%s" to a live node uri. The node was probably deleted.', $matches[0]), LogEnvironment::fromMethodName(__METHOD__)); + } $this->runtime->addCacheTag('node', $matches[2]); break; case 'asset': @@ -183,11 +196,13 @@ function (array $matches) use (&$unresolvedUris, $absolute, $nodeAddress) { /** * Replace the target attribute of link tags in processedContent with the target * specified by externalLinkTarget and resourceLinkTarget options. - * Additionally set rel="noopener" for links with target="_blank". + * Additionally sets rel="noopener" for links with target="_blank", + * and rel="noopener external" for external links. */ protected function replaceLinkTargets(string $processedContent): string { - $noOpenerString = $this->fusionValue('setNoOpener') ? ' rel="noopener"' : ''; + $setNoOpener = $this->fusionValue('setNoOpener'); + $setExternal = $this->fusionValue('setExternal'); $externalLinkTarget = trim($this->fusionValue('externalLinkTarget')); $resourceLinkTarget = trim($this->fusionValue('resourceLinkTarget')); if ($externalLinkTarget === '' && $resourceLinkTarget === '') { @@ -195,31 +210,38 @@ protected function replaceLinkTargets(string $processedContent): string } $controllerContext = $this->runtime->getControllerContext(); $host = $controllerContext->getRequest()->getHttpRequest()->getUri()->getHost(); + // todo optimize to only use one preg_replace_callback for uri converting $processedContent = preg_replace_callback( '~~i', - function ($matches) use ($externalLinkTarget, $resourceLinkTarget, $host, $noOpenerString) { + function ($matches) use ($externalLinkTarget, $resourceLinkTarget, $host, $setNoOpener, $setExternal) { list($linkText, $linkHref) = $matches; $uriHost = parse_url($linkHref, PHP_URL_HOST); $target = null; - if ($externalLinkTarget !== '' && is_string($uriHost) && $uriHost !== $host) { + $isExternalLink = is_string($uriHost) && $uriHost !== $host; + if ($externalLinkTarget !== '' && $isExternalLink) { $target = $externalLinkTarget; } - if ($resourceLinkTarget !== '' && strpos($linkHref, '_Resources') !== false) { + if ($resourceLinkTarget !== '' && str_contains($linkHref, '_Resources')) { $target = $resourceLinkTarget; } if ($target === null) { return $linkText; } - if (preg_match_all('~target="(.*?)~i', $linkText, $targetMatches)) { + // todo merge with "rel" attribute if already existent + $relValue = $isExternalLink && $setNoOpener ? 'noopener ' : ''; + $relValue .= $isExternalLink && $setExternal ? 'external' : ''; + $relValue = ltrim($relValue); + if (str_contains($linkText, 'target="')) { + // todo shouldn't we merge the current target value return preg_replace( - '/target=".*?"/', - sprintf('target="%s"%s', $target, $target === '_blank' ? $noOpenerString : ''), + '/target="[^"]*"/', + sprintf('target="%s"%s', $target, $relValue ? sprintf(' rel="%s"', $relValue) : $relValue), $linkText ); } return str_replace( 'subgraphIdentity->contentRepositoryId; - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( - $node->subgraphIdentity->contentStreamId - ); + + $renderingMode = $this->runtime->fusionGlobals->get('renderingMode'); + assert($renderingMode instanceof RenderingMode); $applicationContext = $this->environment->getContext(); - if ( - $applicationContext->isProduction() - && $this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess') - && !is_null($workspace) - && !$workspace->workspaceName->isLive() - ) { + if (!$renderingMode->isEdit) { + return $output; + } + + if (!$this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess')) { + return $output; + } + + if ($applicationContext->isProduction()) { $output = '
Failed to render element' . $output . '
'; diff --git a/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php b/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php index 3928e1dcc25..fc72801df34 100644 --- a/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php +++ b/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php @@ -24,6 +24,7 @@ use Neos\FluidAdaptor\View\StandaloneView; use Neos\Fusion\Core\ExceptionHandlers\AbstractRenderingExceptionHandler; use Neos\Fusion\Core\ExceptionHandlers\HtmlMessageHandler; +use Neos\Neos\Domain\Model\RenderingMode; use Neos\Neos\Service\ContentElementWrappingService; use Psr\Http\Message\ResponseInterface; @@ -86,14 +87,11 @@ protected function handle($fusionPath, \Exception $exception, $referenceCode) } if (!is_null($documentNode)) { - $contentRepositoryId = $documentNode->subgraphIdentity->contentRepositoryId; - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( - $documentNode->subgraphIdentity->contentStreamId - ); + $renderingMode = $this->runtime->fusionGlobals->get('renderingMode'); + assert($renderingMode instanceof RenderingMode); if ( - $workspace && !$workspace->workspaceName->isLive() - && $this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess') + $this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess') + && $renderingMode->isEdit ) { $isBackend = true; $fluidView->assign( diff --git a/Neos.Neos/Classes/Fusion/NodeUriImplementation.php b/Neos.Neos/Classes/Fusion/NodeUriImplementation.php index d78a4c4e7dd..e4b0f4630b7 100644 --- a/Neos.Neos/Classes/Fusion/NodeUriImplementation.php +++ b/Neos.Neos/Classes/Fusion/NodeUriImplementation.php @@ -129,11 +129,11 @@ public function evaluate() } else { return ''; } - /** @todo implement us + /* TODO implement us see https://github.com/neos/neos-development-collection/issues/4524 {@see \Neos\Neos\ViewHelpers\Uri\NodeViewHelper::resolveNodeAddressFromString} for an example implementation elseif ($node === '~') { $nodeAddress = $this->nodeAddressFactory->createFromNode($node); $nodeAddress = $nodeAddress->withNodeAggregateId( - $this->nodeSiteResolvingService->findSiteNodeForNodeAddress($nodeAddress)->getNodeAggregateId() + $siteNode->nodeAggregateId ); } elseif (is_string($node) && substr($node, 0, 7) === 'node://') { $nodeAddress = $this->nodeAddressFactory->createFromNode($node); diff --git a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php index 4f4a640b9b3..31cd1ecabf1 100644 --- a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php +++ b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php @@ -17,8 +17,10 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Comparator; -use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\EventInterface; @@ -35,6 +37,7 @@ use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodeSpecializationVariantWasCreated; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\RootWorkspaceWasCreated; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; +use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -50,6 +53,8 @@ */ class ChangeProjection implements ProjectionInterface { + private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; + /** * @var ChangeFinder|null Cache for the ChangeFinder returned by {@see getState()}, * so that always the same instance is returned @@ -103,31 +108,18 @@ private function setupTables(): void } } - $schema = new Schema(); - $changeTable = $schema->createTable($this->tableNamePrefix); - $changeTable->addColumn('contentStreamId', Types::STRING) - ->setLength(40) - ->setNotnull(true); - $changeTable->addColumn('created', Types::BOOLEAN) - ->setNotnull(true); - $changeTable->addColumn('changed', Types::BOOLEAN) - ->setNotnull(true); - $changeTable->addColumn('moved', Types::BOOLEAN) - ->setNotnull(true); - - $changeTable->addColumn('nodeAggregateId', Types::STRING) - ->setLength(64) - ->setNotnull(true); - $changeTable->addColumn('originDimensionSpacePoint', Types::TEXT) - ->setNotnull(false); - $changeTable->addColumn('originDimensionSpacePointHash', Types::STRING) - ->setLength(255) - ->setNotnull(true); - $changeTable->addColumn('deleted', Types::BOOLEAN) - ->setNotnull(true); - $changeTable->addColumn('removalAttachmentPoint', Types::STRING) - ->setLength(255) - ->setNotnull(false); + $changeTable = new Table($this->tableNamePrefix, [ + DbalSchemaFactory::columnForContentStreamId('contentStreamId')->setNotNull(true), + (new Column('created', Type::getType(Types::BOOLEAN)))->setNotnull(true), + (new Column('changed', Type::getType(Types::BOOLEAN)))->setNotnull(true), + (new Column('moved', Type::getType(Types::BOOLEAN)))->setNotnull(true), + DbalSchemaFactory::columnForNodeAggregateId('nodeAggregateId')->setNotNull(true), + DbalSchemaFactory::columnForDimensionSpacePoint('originDimensionSpacePoint')->setNotNull(false), + DbalSchemaFactory::columnForDimensionSpacePointHash('originDimensionSpacePointHash')->setNotNull(true), + (new Column('deleted', Type::getType(Types::BOOLEAN)))->setNotnull(true), + // Despite the name suggesting this might be an anchor point of sorts, this is a nodeAggregateId type + DbalSchemaFactory::columnForNodeAggregateId('removalAttachmentPoint')->setNotNull(false) + ]); $changeTable->setPrimaryKey([ 'contentStreamId', @@ -135,17 +127,14 @@ private function setupTables(): void 'originDimensionSpacePointHash' ]); - $liveContentStreamsTable = $schema->createTable($this->tableNamePrefix . '_livecontentstreams'); - $liveContentStreamsTable->addColumn('contentstreamid', Types::STRING) - ->setLength(40) - ->setDefault('') - ->setNotnull(true); - $liveContentStreamsTable->addColumn('workspacename', Types::STRING) - ->setLength(255) - ->setDefault('') - ->setNotnull(true); + $liveContentStreamsTable = new Table($this->tableNamePrefix . '_livecontentstreams', [ + DbalSchemaFactory::ColumnForContentStreamId('contentstreamid')->setNotNull(true), + (new Column('workspacename', Type::getType(Types::STRING)))->setLength(255)->setDefault('')->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION) + ]); $liveContentStreamsTable->setPrimaryKey(['contentstreamid']); + $schema = DbalSchemaFactory::createSchemaWithTables($schemaManager, [$changeTable, $liveContentStreamsTable]); + $schemaDiff = (new Comparator())->compare($schemaManager->createSchema(), $schema); foreach ($schemaDiff->toSaveSql($connection->getDatabasePlatform()) as $statement) { $connection->executeStatement($statement); diff --git a/Neos.Neos/Classes/Service/ContentElementEditableService.php b/Neos.Neos/Classes/Service/ContentElementEditableService.php index 811dc634a65..16e2159fa9a 100644 --- a/Neos.Neos/Classes/Service/ContentElementEditableService.php +++ b/Neos.Neos/Classes/Service/ContentElementEditableService.php @@ -14,14 +14,12 @@ namespace Neos\Neos\Service; -use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Security\Authorization\PrivilegeManagerInterface; use Neos\Fusion\Service\HtmlAugmenter as FusionHtmlAugmenter; +use Neos\Neos\FrontendRouting\NodeAddressFactory; /** * The content element editable service adds the necessary markup around @@ -56,15 +54,6 @@ public function wrapContentProperty(Node $node, string $property, string $conten $node->subgraphIdentity->contentRepositoryId ); - if ( - $this->isContentStreamOfLiveWorkspace( - $node->subgraphIdentity->contentStreamId, - $contentRepository - ) - ) { - return $content; - } - // TODO: permissions //if (!$this->nodeAuthorizationService->isGrantedToEditNode($node)) { // return $content; @@ -79,13 +68,4 @@ public function wrapContentProperty(Node $node, string $property, string $conten return $this->htmlAugmenter->addAttributes($content, $attributes, 'span'); } - - private function isContentStreamOfLiveWorkspace( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository - ): bool { - return $contentRepository->getWorkspaceFinder() - ->findOneByCurrentContentStreamId($contentStreamId) - ?->workspaceName->isLive() ?: false; - } } diff --git a/Neos.Neos/Classes/Service/ContentElementWrappingService.php b/Neos.Neos/Classes/Service/ContentElementWrappingService.php index 4851b49da2e..47b224e334c 100644 --- a/Neos.Neos/Classes/Service/ContentElementWrappingService.php +++ b/Neos.Neos/Classes/Service/ContentElementWrappingService.php @@ -14,15 +14,13 @@ namespace Neos\Neos\Service; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\Neos\FrontendRouting\NodeAddressFactory; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Security\Authorization\PrivilegeManagerInterface; use Neos\Flow\Session\SessionInterface; use Neos\Fusion\Service\HtmlAugmenter as FusionHtmlAugmenter; +use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\Ui\Domain\Service\UserLocaleService; use Neos\Neos\Ui\Fusion\Helper\NodeInfoHelper; @@ -86,15 +84,6 @@ public function wrapContentObject( $node->subgraphIdentity->contentRepositoryId ); - if ( - $this->isContentStreamOfLiveWorkspace( - $node->subgraphIdentity->contentStreamId, - $contentRepository - ) - ) { - return $content; - } - // TODO: reenable permissions //if ($this->nodeAuthorizationService->isGrantedToEditNode($node) === false) { // return $content; @@ -134,12 +123,10 @@ public function wrapCurrentDocumentMetadata( string $fusionPath, array $additionalAttributes = [], ): string { - $contentRepository = $this->contentRepositoryRegistry->get( - $node->subgraphIdentity->contentRepositoryId - ); - if ($this->needsMetadata($node, $contentRepository, true) === false) { - return $content; - } + // TODO: reenable permissions + //if ($this->nodeAuthorizationService->isGrantedToEditNode($node) === false) { + // return $content; + //} $attributes = $additionalAttributes; $attributes['data-__neos-fusion-path'] = $fusionPath; @@ -168,26 +155,4 @@ protected function addCssClasses(array $attributes, Node $node, array $initialCl return $attributes; } - - protected function needsMetadata( - Node $node, - ContentRepository $contentRepository, - bool $renderCurrentDocumentMetadata - ): bool { - return $this->isContentStreamOfLiveWorkspace( - $node->subgraphIdentity->contentStreamId, - $contentRepository - ) - && ($renderCurrentDocumentMetadata === true - /* TODO: permissions || $this->nodeAuthorizationService->isGrantedToEditNode($node) === true */); - } - - private function isContentStreamOfLiveWorkspace( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository - ): bool { - return $contentRepository->getWorkspaceFinder() - ->findOneByCurrentContentStreamId($contentStreamId) - ?->workspaceName->isLive() ?: false; - } } diff --git a/Neos.Neos/Classes/ViewHelpers/ContentElement/EditableViewHelper.php b/Neos.Neos/Classes/ViewHelpers/ContentElement/EditableViewHelper.php index 5be087be0f3..d6f77451288 100644 --- a/Neos.Neos/Classes/ViewHelpers/ContentElement/EditableViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/ContentElement/EditableViewHelper.php @@ -20,6 +20,7 @@ use Neos\FluidAdaptor\Core\ViewHelper\AbstractTagBasedViewHelper; use Neos\FluidAdaptor\Core\ViewHelper\Exception as ViewHelperException; use Neos\Fusion\ViewHelpers\FusionContextTrait; +use Neos\Neos\Domain\Model\RenderingMode; use Neos\Neos\Service\ContentElementEditableService; /** @@ -114,7 +115,15 @@ public function render(): string } $this->tag->setContent($content); - return $this->contentElementEditableService->wrapContentProperty($node, $propertyName, (string)$this->tag->render()); + $output = (string)$this->tag->render(); + + $renderingMode = $this->getContextVariable('renderingMode'); + assert($renderingMode instanceof RenderingMode); + if (!$renderingMode->isEdit) { + return $output; + } + + return $this->contentElementEditableService->wrapContentProperty($node, $propertyName, $output); } /** diff --git a/Neos.Neos/Classes/ViewHelpers/ContentElement/WrapViewHelper.php b/Neos.Neos/Classes/ViewHelpers/ContentElement/WrapViewHelper.php index d24056239e5..dd19d456035 100644 --- a/Neos.Neos/Classes/ViewHelpers/ContentElement/WrapViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/ContentElement/WrapViewHelper.php @@ -19,6 +19,7 @@ use Neos\FluidAdaptor\Core\ViewHelper\AbstractViewHelper; use Neos\FluidAdaptor\Core\ViewHelper\Exception as ViewHelperException; use Neos\Fusion\FusionObjects\Helpers\FusionAwareViewInterface; +use Neos\Neos\Domain\Model\RenderingMode; use Neos\Neos\Service\ContentElementWrappingService; /** @@ -85,6 +86,13 @@ public function render(): string 1645650713 ); } + + $renderingMode = $fusionObject->getRuntime()->fusionGlobals->get('renderingMode'); + assert($renderingMode instanceof RenderingMode); + if (!$renderingMode->isEdit) { + return (string)$this->renderChildren(); + } + $currentContext = $fusionObject->getRuntime()->getCurrentContext(); $node = $this->arguments['node'] ?? $currentContext['node']; diff --git a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php index bb81fab4a05..f75ef066aa9 100644 --- a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php @@ -15,6 +15,7 @@ namespace Neos\Neos\ViewHelpers\Link; use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; @@ -32,7 +33,6 @@ use Neos\FluidAdaptor\Core\ViewHelper\AbstractTagBasedViewHelper; use Neos\FluidAdaptor\Core\ViewHelper\Exception as ViewHelperException; use Neos\Fusion\ViewHelpers\FusionContextTrait; -use Neos\Neos\Domain\Service\NodeSiteResolvingService; use Neos\Neos\FrontendRouting\Exception\InvalidShortcutException; use Neos\Neos\FrontendRouting\Exception\NodeNotFoundException; use Neos\Neos\FrontendRouting\NodeShortcutResolver; @@ -137,12 +137,6 @@ class NodeViewHelper extends AbstractTagBasedViewHelper */ protected $tagName = 'a'; - /** - * @Flow\Inject - * @var NodeSiteResolvingService - */ - protected $nodeSiteResolvingService; - /** * @Flow\Inject * @var NodeShortcutResolver @@ -382,12 +376,7 @@ private function resolveNodeAddressFromString( VisibilityConstraints::withoutRestrictions() ); if (strncmp($path, '~', 1) === 0) { - // TODO: This can be simplified - // once https://github.com/neos/contentrepository-development-collection/issues/164 is resolved - $siteNode = $this->nodeSiteResolvingService->findSiteNodeForNodeAddress( - $documentNodeAddress, - $documentNode->subgraphIdentity->contentRepositoryId - ); + $siteNode = $subgraph->findClosestNode($documentNodeAddress->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE)); if ($siteNode === null) { throw new ViewHelperException(sprintf( 'Failed to determine site node for aggregate node "%s" and subgraph "%s"', diff --git a/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php index 6cdcb6350c5..67438d4afb5 100644 --- a/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php @@ -14,10 +14,11 @@ namespace Neos\Neos\ViewHelpers\Uri; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphIdentity; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; +use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Neos\Neos\FrontendRouting\NodeAddress; use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; @@ -31,7 +32,6 @@ use Neos\FluidAdaptor\Core\ViewHelper\AbstractViewHelper; use Neos\FluidAdaptor\Core\ViewHelper\Exception as ViewHelperException; use Neos\Fusion\ViewHelpers\FusionContextTrait; -use Neos\Neos\Domain\Service\NodeSiteResolvingService; use Neos\Neos\FrontendRouting\NodeUriBuilder; /** @@ -112,12 +112,6 @@ class NodeViewHelper extends AbstractViewHelper */ protected $contentRepositoryRegistry; - /** - * @Flow\Inject - * @var NodeSiteResolvingService - */ - protected $nodeSiteResolvingService; - /** * @Flow\Inject * @var ThrowableStorageInterface @@ -285,12 +279,7 @@ private function resolveNodeAddressFromString(string $path): ?NodeAddress VisibilityConstraints::withoutRestrictions() ); if (strncmp($path, '~', 1) === 0) { - // TODO: This can be simplified - // once https://github.com/neos/contentrepository-development-collection/issues/164 is resolved - $siteNode = $this->nodeSiteResolvingService->findSiteNodeForNodeAddress( - $documentNodeAddress, - $documentNode->subgraphIdentity->contentRepositoryId - ); + $siteNode = $subgraph->findClosestNode($documentNodeAddress->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE)); if ($siteNode === null) { throw new ViewHelperException(sprintf( 'Failed to determine site node for aggregate node "%s" and subgraph "%s"', diff --git a/Neos.Neos/Configuration/Settings.yaml b/Neos.Neos/Configuration/Settings.yaml index 6580ceda555..e07291dfe88 100755 --- a/Neos.Neos/Configuration/Settings.yaml +++ b/Neos.Neos/Configuration/Settings.yaml @@ -66,6 +66,10 @@ Neos: label: 'Neos.Neos:Main:nodeTypes.groups.plugins' collapsed: true + frontend: + # HTTP Status Code used for redirecting Neos.Neos:Shortcut to target + shortcutRedirectHttpStatusCode: 303 + userInterface: # Switch on to see all translated labels getting scrambled. You now can localize diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/7317.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/7317.rst new file mode 100644 index 00000000000..a323dd9be01 --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/7317.rst @@ -0,0 +1,144 @@ +`7.3.17 (2023-12-12) `_ +================================================================================================ + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Remove unnecessary basemixins dependency for ContentReferences `_ +----------------------------------------------------------------------------------------------------------------------------------------- + +The package has no direct dependency to the basemixins and should be usable without the other basemixins. + +* Packages: ``NodeTypes.ContentReferences`` + +`BUGFIX: Add workspace hash to NodeDynamicTag_ and AssetDynamicTag_ cache tags `_ +------------------------------------------------------------------------------------------------------------------------------------------------ + +* Fixes: `#4781 `_ + +* Packages: ``Neos`` + +`BUGFIX: Filter for assets by asset collection without overriding existing WHERE conditions `_ +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +The query to fetch assets gets build in multiple steps. E.g in ``findAll`` it creates the query, adds the "variant filter clause" and afterwards the "asset collection filter clause". + +``` + public function findAll(AssetCollection $assetCollection = null): QueryResultInterface + { + $query = $this->createQuery(); + $this->addAssetVariantFilterClause($query); + $this->addAssetCollectionToQueryConstraints($query, $assetCollection); + return $query->execute(); + } +``` +But adding the "asset collection filter clause" removes/overrides the existing "variant filter clause" + +This fix replaces the way of setting "asset collection filter clause", so the existing where clauses are retained. + +* Fixes: `#4723 `_ + +* Packages: ``Media`` + +`BUGFIX: flashmessages in legacy modules work as expected `_ +--------------------------------------------------------------------------------------------------------------------------- + +This applies the, since a while unused, presets for message severity types to notifications in classic Neos backend modules again so that eg. warnings and errors stay on display and can be copied / screenshotted. + +Also regenerates js maps that seem to have compiled wrongly before. + +* Fixes: `#4672 `_ + +* Packages: ``Neos`` + +`BUGFIX: Check if image is possible to refit on replacement `_ +----------------------------------------------------------------------------------------------------------------------------- + +* Fixes: `#4752 `_ + +Checks if the replacement is an image and has a size. + +* Packages: ``Neos`` ``NodeTypes.BaseMixins`` ``Media`` + +`BUGFIX: Allow disabling of auto-created Image Variants `_ +------------------------------------------------------------------------------------------------------------------------- + +Fixes support for the setting ``autoCreateImageVariantPresets`` that was documented for a long time but never actually evaluated. + +This change set: + +* Adjusts ``AssetService::assetCreated()`` signal to only trigger ``AssetVariantGenerator::createVariants()`` if the ``autoCreateImageVariantPresets`` flag is set +* Sets the default value of the flag to ``true`` for greater backwards compatibility +* Adjusts ``AssetVariantGenerator::createVariant()`` to only create a variant if it does not exist already – previously multiple variants with the same identifiers could be created for a single asset leading to undeterministic behavior +* Adds a button "Create missing Variants" to the ``Variants`` tab of the Media Module allowing editors to manually trigger creation of (missing) variants. + +* Fixes: `#4300 `_ + +* Packages: ``Neos`` ``Media`` + +`BUGFIX: `props` will be unset after an exception `_ +------------------------------------------------------------------------------------------------------------------- + +* Resolves: `#4525 `_ + +The rendering in a Neos.Fusion Component had a bug where the ``props`` might be undefined if an exception happened earlier in an eel expression. + +This was caused by not correctly poping the runtimes context and thus causing a unexpected shift in the context stack. + +**Upgrade instructions** + +**Review instructions** + + +* Packages: ``Neos`` ``Fusion`` + +`TASK: Use role label in list users/new user view if available `_ +-------------------------------------------------------------------------------------------------------------------------------- + +In Neos 7 (and with https://github.com/neos/flow-development-collection/issues/2162), role labels were introduced. While we now have a nice table view in the "edit account" view, the role label is not displayed anywhere else. + +I'm aware that Neos 7 and 8 are in maintenance-only mode, but I think we all agree that Neos 8 will be around for quite a while. I suggest the minimal change to use the role label in the user list and the "new user" view if there is one. + +- [N/A] Code follows the PSR-2 coding style +- [N/A] Tests have been created, run and adjusted as needed +- [x] The PR is created against the `lowest maintained branch `_ +- [x] Reviewer - PR Title is brief but complete and starts with ``FEATURE|TASK|BUGFIX`` +- [x] Reviewer - The first section explains the change briefly for change-logs +- [N/A] Reviewer - Breaking Changes are marked with ``!!!`` and have upgrade-instructions + +* Packages: ``Neos`` + +`TASK: Adjust neos/neos css build to work on apple silicon `_ +---------------------------------------------------------------------------------------------------------------------------- + + + +* Packages: ``Neos`` + +`TASK: Add support information to package composer.json `_ +------------------------------------------------------------------------------------------------------------------------- + +The git url will be useful for automating the split configuration later. + +* Packages: ``Neos`` ``Media`` + +`TASK: Tweak dependency on neos/twitter-bootstrap `_ +------------------------------------------------------------------------------------------------------------------- + +- move the dependency from ``neos/neos`` to ``neos/media-browser`` +- change from ``*`` to ``^3.0.6`` (the first version allowing Neos 7.x) + + +* Packages: ``Neos`` ``Media.Browser`` + +`TASK: All dependencies within collection point to `self.version` `_ +----------------------------------------------------------------------------------------------------------------------------------- + +Re-adjusts dependencies to point to ``self.version`` for easier maintenance. + +* Fixes: `#4257 `_ + +* Packages: ``Neos`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/7318.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/7318.rst new file mode 100644 index 00000000000..6ffe45df385 --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/7318.rst @@ -0,0 +1,19 @@ +`7.3.18 (2023-12-13) `_ +================================================================================================ + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Filter for assets by asset collection by using constraints `_ +------------------------------------------------------------------------------------------------------------------------------------- + +Fixes a bug, that was introduced with: https://github.com/neos/neos-development-collection/pull/4724 + +Because of using the QueryBuilder twice, the count of the paramter and for the query is not correct anymore. + +* Fixes: `#4801 `_ + +* Packages: ``Neos`` ``Media`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/8014.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/8014.rst new file mode 100644 index 00000000000..3d5cece9e9e --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/8014.rst @@ -0,0 +1,150 @@ +`8.0.14 (2023-12-12) `_ +================================================================================================ + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Remove unnecessary basemixins dependency for ContentReferences `_ +----------------------------------------------------------------------------------------------------------------------------------------- + +The package has no direct dependency to the basemixins and should be usable without the other basemixins. + +* Packages: ``NodeTypes.ContentReferences`` + +`BUGFIX: Add workspace hash to NodeDynamicTag_ and AssetDynamicTag_ cache tags `_ +------------------------------------------------------------------------------------------------------------------------------------------------ + +* Fixes: `#4781 `_ + +* Packages: ``Neos`` + +`BUGFIX: Filter for assets by asset collection without overriding existing WHERE conditions `_ +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +The query to fetch assets gets build in multiple steps. E.g in ``findAll`` it creates the query, adds the "variant filter clause" and afterwards the "asset collection filter clause". + +``` + public function findAll(AssetCollection $assetCollection = null): QueryResultInterface + { + $query = $this->createQuery(); + $this->addAssetVariantFilterClause($query); + $this->addAssetCollectionToQueryConstraints($query, $assetCollection); + return $query->execute(); + } +``` +But adding the "asset collection filter clause" removes/overrides the existing "variant filter clause" + +This fix replaces the way of setting "asset collection filter clause", so the existing where clauses are retained. + +* Fixes: `#4723 `_ + +* Packages: ``Media`` + +`BUGFIX: flashmessages in legacy modules work as expected `_ +--------------------------------------------------------------------------------------------------------------------------- + +This applies the, since a while unused, presets for message severity types to notifications in classic Neos backend modules again so that eg. warnings and errors stay on display and can be copied / screenshotted. + +Also regenerates js maps that seem to have compiled wrongly before. + +* Fixes: `#4672 `_ + +* Packages: ``Neos`` + +`BUGFIX: Check if image is possible to refit on replacement `_ +----------------------------------------------------------------------------------------------------------------------------- + +* Fixes: `#4752 `_ + +Checks if the replacement is an image and has a size. + +* Packages: ``Neos`` ``NodeTypes.BaseMixins`` ``Media`` + +`BUGFIX: Allow disabling of auto-created Image Variants `_ +------------------------------------------------------------------------------------------------------------------------- + +Fixes support for the setting ``autoCreateImageVariantPresets`` that was documented for a long time but never actually evaluated. + +This change set: + +* Adjusts ``AssetService::assetCreated()`` signal to only trigger ``AssetVariantGenerator::createVariants()`` if the ``autoCreateImageVariantPresets`` flag is set +* Sets the default value of the flag to ``true`` for greater backwards compatibility +* Adjusts ``AssetVariantGenerator::createVariant()`` to only create a variant if it does not exist already – previously multiple variants with the same identifiers could be created for a single asset leading to undeterministic behavior +* Adds a button "Create missing Variants" to the ``Variants`` tab of the Media Module allowing editors to manually trigger creation of (missing) variants. + +* Fixes: `#4300 `_ + +* Packages: ``Neos`` ``Media`` + +`BUGFIX: `props` will be unset after an exception `_ +------------------------------------------------------------------------------------------------------------------- + +* Resolves: `#4525 `_ + +The rendering in a Neos.Fusion Component had a bug where the ``props`` might be undefined if an exception happened earlier in an eel expression. + +This was caused by not correctly poping the runtimes context and thus causing a unexpected shift in the context stack. + +**Upgrade instructions** + + +* Packages: ``Neos`` ``Fusion`` + +`TASK: Use role label in list users/new user view if available `_ +-------------------------------------------------------------------------------------------------------------------------------- + +In Neos 7 (and with https://github.com/neos/flow-development-collection/issues/2162), role labels were introduced. While we now have a nice table view in the "edit account" view, the role label is not displayed anywhere else. + +I'm aware that Neos 7 and 8 are in maintenance-only mode, but I think we all agree that Neos 8 will be around for quite a while. I suggest the minimal change to use the role label in the user list and the "new user" view if there is one. + +- [N/A] Code follows the PSR-2 coding style +- [N/A] Tests have been created, run and adjusted as needed +- [x] The PR is created against the `lowest maintained branch `_ +- [x] Reviewer - PR Title is brief but complete and starts with ``FEATURE|TASK|BUGFIX`` +- [x] Reviewer - The first section explains the change briefly for change-logs +- [N/A] Reviewer - Breaking Changes are marked with ``!!!`` and have upgrade-instructions + +* Packages: ``Neos`` + +`TASK: Add PHP 8.3 to build workflow matrix `_ +------------------------------------------------------------------------------------------------------------- + +This will test Flow against PHP 8.3 + + +* Packages: ``Neos`` ``.github`` + +`TASK: Adjust neos/neos css build to work on apple silicon `_ +---------------------------------------------------------------------------------------------------------------------------- + + + +* Packages: ``Neos`` + +`TASK: Add support information to package composer.json `_ +------------------------------------------------------------------------------------------------------------------------- + +The git url will be useful for automating the split configuration later. + +* Packages: ``Neos`` ``Media`` + +`TASK: Tweak dependency on neos/twitter-bootstrap `_ +------------------------------------------------------------------------------------------------------------------- + +- move the dependency from ``neos/neos`` to ``neos/media-browser`` +- change from ``*`` to ``^3.0.6`` (the first version allowing Neos 7.x) + + +* Packages: ``Neos`` ``Media.Browser`` + +`TASK: All dependencies within collection point to `self.version` `_ +----------------------------------------------------------------------------------------------------------------------------------- + +Re-adjusts dependencies to point to ``self.version`` for easier maintenance. + +* Fixes: `#4257 `_ + +* Packages: ``Neos`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/8015.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/8015.rst new file mode 100644 index 00000000000..b52021deb81 --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/8015.rst @@ -0,0 +1,19 @@ +`8.0.15 (2023-12-13) `_ +================================================================================================ + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Filter for assets by asset collection by using constraints `_ +------------------------------------------------------------------------------------------------------------------------------------- + +Fixes a bug, that was introduced with: https://github.com/neos/neos-development-collection/pull/4724 + +Because of using the QueryBuilder twice, the count of the paramter and for the query is not correct anymore. + +* Fixes: `#4801 `_ + +* Packages: ``Neos`` ``Media`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/8110.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/8110.rst new file mode 100644 index 00000000000..26ba1ddd4c5 --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/8110.rst @@ -0,0 +1,19 @@ +`8.1.10 (2023-12-13) `_ +================================================================================================ + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Filter for assets by asset collection by using constraints `_ +------------------------------------------------------------------------------------------------------------------------------------- + +Fixes a bug, that was introduced with: https://github.com/neos/neos-development-collection/pull/4724 + +Because of using the QueryBuilder twice, the count of the paramter and for the query is not correct anymore. + +* Fixes: `#4801 `_ + +* Packages: ``Neos`` ``Media`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/819.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/819.rst new file mode 100644 index 00000000000..9ddee03ac29 --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/819.rst @@ -0,0 +1,150 @@ +`8.1.9 (2023-12-12) `_ +============================================================================================== + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Remove unnecessary basemixins dependency for ContentReferences `_ +----------------------------------------------------------------------------------------------------------------------------------------- + +The package has no direct dependency to the basemixins and should be usable without the other basemixins. + +* Packages: ``NodeTypes.ContentReferences`` + +`BUGFIX: Add workspace hash to NodeDynamicTag_ and AssetDynamicTag_ cache tags `_ +------------------------------------------------------------------------------------------------------------------------------------------------ + +* Fixes: `#4781 `_ + +* Packages: ``Neos`` + +`BUGFIX: Filter for assets by asset collection without overriding existing WHERE conditions `_ +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +The query to fetch assets gets build in multiple steps. E.g in ``findAll`` it creates the query, adds the "variant filter clause" and afterwards the "asset collection filter clause". + +``` + public function findAll(AssetCollection $assetCollection = null): QueryResultInterface + { + $query = $this->createQuery(); + $this->addAssetVariantFilterClause($query); + $this->addAssetCollectionToQueryConstraints($query, $assetCollection); + return $query->execute(); + } +``` +But adding the "asset collection filter clause" removes/overrides the existing "variant filter clause" + +This fix replaces the way of setting "asset collection filter clause", so the existing where clauses are retained. + +* Fixes: `#4723 `_ + +* Packages: ``Media`` + +`BUGFIX: flashmessages in legacy modules work as expected `_ +--------------------------------------------------------------------------------------------------------------------------- + +This applies the, since a while unused, presets for message severity types to notifications in classic Neos backend modules again so that eg. warnings and errors stay on display and can be copied / screenshotted. + +Also regenerates js maps that seem to have compiled wrongly before. + +* Fixes: `#4672 `_ + +* Packages: ``Neos`` + +`BUGFIX: Check if image is possible to refit on replacement `_ +----------------------------------------------------------------------------------------------------------------------------- + +* Fixes: `#4752 `_ + +Checks if the replacement is an image and has a size. + +* Packages: ``Neos`` ``NodeTypes.BaseMixins`` ``Media`` + +`BUGFIX: Allow disabling of auto-created Image Variants `_ +------------------------------------------------------------------------------------------------------------------------- + +Fixes support for the setting ``autoCreateImageVariantPresets`` that was documented for a long time but never actually evaluated. + +This change set: + +* Adjusts ``AssetService::assetCreated()`` signal to only trigger ``AssetVariantGenerator::createVariants()`` if the ``autoCreateImageVariantPresets`` flag is set +* Sets the default value of the flag to ``true`` for greater backwards compatibility +* Adjusts ``AssetVariantGenerator::createVariant()`` to only create a variant if it does not exist already – previously multiple variants with the same identifiers could be created for a single asset leading to undeterministic behavior +* Adds a button "Create missing Variants" to the ``Variants`` tab of the Media Module allowing editors to manually trigger creation of (missing) variants. + +* Fixes: `#4300 `_ + +* Packages: ``Neos`` ``Media`` + +`BUGFIX: `props` will be unset after an exception `_ +------------------------------------------------------------------------------------------------------------------- + +* Resolves: `#4525 `_ + +The rendering in a Neos.Fusion Component had a bug where the ``props`` might be undefined if an exception happened earlier in an eel expression. + +This was caused by not correctly poping the runtimes context and thus causing a unexpected shift in the context stack. + +**Upgrade instructions** + + +* Packages: ``Neos`` ``Fusion`` + +`TASK: Use role label in list users/new user view if available `_ +-------------------------------------------------------------------------------------------------------------------------------- + +In Neos 7 (and with https://github.com/neos/flow-development-collection/issues/2162), role labels were introduced. While we now have a nice table view in the "edit account" view, the role label is not displayed anywhere else. + +I'm aware that Neos 7 and 8 are in maintenance-only mode, but I think we all agree that Neos 8 will be around for quite a while. I suggest the minimal change to use the role label in the user list and the "new user" view if there is one. + +- [N/A] Code follows the PSR-2 coding style +- [N/A] Tests have been created, run and adjusted as needed +- [x] The PR is created against the `lowest maintained branch `_ +- [x] Reviewer - PR Title is brief but complete and starts with ``FEATURE|TASK|BUGFIX`` +- [x] Reviewer - The first section explains the change briefly for change-logs +- [N/A] Reviewer - Breaking Changes are marked with ``!!!`` and have upgrade-instructions + +* Packages: ``Neos`` + +`TASK: Add PHP 8.3 to build workflow matrix `_ +------------------------------------------------------------------------------------------------------------- + +This will test Flow against PHP 8.3 + + +* Packages: ``Neos`` ``.github`` + +`TASK: Adjust neos/neos css build to work on apple silicon `_ +---------------------------------------------------------------------------------------------------------------------------- + + + +* Packages: ``Neos`` + +`TASK: Add support information to package composer.json `_ +------------------------------------------------------------------------------------------------------------------------- + +The git url will be useful for automating the split configuration later. + +* Packages: ``Neos`` ``Media`` + +`TASK: Tweak dependency on neos/twitter-bootstrap `_ +------------------------------------------------------------------------------------------------------------------- + +- move the dependency from ``neos/neos`` to ``neos/media-browser`` +- change from ``*`` to ``^3.0.6`` (the first version allowing Neos 7.x) + + +* Packages: ``Neos`` ``Media.Browser`` + +`TASK: All dependencies within collection point to `self.version` `_ +----------------------------------------------------------------------------------------------------------------------------------- + +Re-adjusts dependencies to point to ``self.version`` for easier maintenance. + +* Fixes: `#4257 `_ + +* Packages: ``Neos`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/8210.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/8210.rst new file mode 100644 index 00000000000..aa60e9ba03e --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/8210.rst @@ -0,0 +1,19 @@ +`8.2.10 (2023-12-13) `_ +================================================================================================ + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Filter for assets by asset collection by using constraints `_ +------------------------------------------------------------------------------------------------------------------------------------- + +Fixes a bug, that was introduced with: https://github.com/neos/neos-development-collection/pull/4724 + +Because of using the QueryBuilder twice, the count of the paramter and for the query is not correct anymore. + +* Fixes: `#4801 `_ + +* Packages: ``Neos`` ``Media`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/829.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/829.rst new file mode 100644 index 00000000000..b139aeaf6d1 --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/829.rst @@ -0,0 +1,150 @@ +`8.2.9 (2023-12-12) `_ +============================================================================================== + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Remove unnecessary basemixins dependency for ContentReferences `_ +----------------------------------------------------------------------------------------------------------------------------------------- + +The package has no direct dependency to the basemixins and should be usable without the other basemixins. + +* Packages: ``NodeTypes.ContentReferences`` + +`BUGFIX: Add workspace hash to NodeDynamicTag_ and AssetDynamicTag_ cache tags `_ +------------------------------------------------------------------------------------------------------------------------------------------------ + +* Fixes: `#4781 `_ + +* Packages: ``Neos`` + +`BUGFIX: Filter for assets by asset collection without overriding existing WHERE conditions `_ +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +The query to fetch assets gets build in multiple steps. E.g in ``findAll`` it creates the query, adds the "variant filter clause" and afterwards the "asset collection filter clause". + +``` + public function findAll(AssetCollection $assetCollection = null): QueryResultInterface + { + $query = $this->createQuery(); + $this->addAssetVariantFilterClause($query); + $this->addAssetCollectionToQueryConstraints($query, $assetCollection); + return $query->execute(); + } +``` +But adding the "asset collection filter clause" removes/overrides the existing "variant filter clause" + +This fix replaces the way of setting "asset collection filter clause", so the existing where clauses are retained. + +* Fixes: `#4723 `_ + +* Packages: ``Media`` + +`BUGFIX: flashmessages in legacy modules work as expected `_ +--------------------------------------------------------------------------------------------------------------------------- + +This applies the, since a while unused, presets for message severity types to notifications in classic Neos backend modules again so that eg. warnings and errors stay on display and can be copied / screenshotted. + +Also regenerates js maps that seem to have compiled wrongly before. + +* Fixes: `#4672 `_ + +* Packages: ``Neos`` + +`BUGFIX: Check if image is possible to refit on replacement `_ +----------------------------------------------------------------------------------------------------------------------------- + +* Fixes: `#4752 `_ + +Checks if the replacement is an image and has a size. + +* Packages: ``Neos`` ``NodeTypes.BaseMixins`` ``Media`` + +`BUGFIX: Allow disabling of auto-created Image Variants `_ +------------------------------------------------------------------------------------------------------------------------- + +Fixes support for the setting ``autoCreateImageVariantPresets`` that was documented for a long time but never actually evaluated. + +This change set: + +* Adjusts ``AssetService::assetCreated()`` signal to only trigger ``AssetVariantGenerator::createVariants()`` if the ``autoCreateImageVariantPresets`` flag is set +* Sets the default value of the flag to ``true`` for greater backwards compatibility +* Adjusts ``AssetVariantGenerator::createVariant()`` to only create a variant if it does not exist already – previously multiple variants with the same identifiers could be created for a single asset leading to undeterministic behavior +* Adds a button "Create missing Variants" to the ``Variants`` tab of the Media Module allowing editors to manually trigger creation of (missing) variants. + +* Fixes: `#4300 `_ + +* Packages: ``Neos`` ``Media`` + +`BUGFIX: `props` will be unset after an exception `_ +------------------------------------------------------------------------------------------------------------------- + +* Resolves: `#4525 `_ + +The rendering in a Neos.Fusion Component had a bug where the ``props`` might be undefined if an exception happened earlier in an eel expression. + +This was caused by not correctly poping the runtimes context and thus causing a unexpected shift in the context stack. + +**Upgrade instructions** + + +* Packages: ``Neos`` ``Fusion`` + +`TASK: Use role label in list users/new user view if available `_ +-------------------------------------------------------------------------------------------------------------------------------- + +In Neos 7 (and with https://github.com/neos/flow-development-collection/issues/2162), role labels were introduced. While we now have a nice table view in the "edit account" view, the role label is not displayed anywhere else. + +I'm aware that Neos 7 and 8 are in maintenance-only mode, but I think we all agree that Neos 8 will be around for quite a while. I suggest the minimal change to use the role label in the user list and the "new user" view if there is one. + +- [N/A] Code follows the PSR-2 coding style +- [N/A] Tests have been created, run and adjusted as needed +- [x] The PR is created against the `lowest maintained branch `_ +- [x] Reviewer - PR Title is brief but complete and starts with ``FEATURE|TASK|BUGFIX`` +- [x] Reviewer - The first section explains the change briefly for change-logs +- [N/A] Reviewer - Breaking Changes are marked with ``!!!`` and have upgrade-instructions + +* Packages: ``Neos`` + +`TASK: Add PHP 8.3 to build workflow matrix `_ +------------------------------------------------------------------------------------------------------------- + +This will test Flow against PHP 8.3 + + +* Packages: ``Neos`` ``.github`` + +`TASK: Adjust neos/neos css build to work on apple silicon `_ +---------------------------------------------------------------------------------------------------------------------------- + + + +* Packages: ``Neos`` + +`TASK: Add support information to package composer.json `_ +------------------------------------------------------------------------------------------------------------------------- + +The git url will be useful for automating the split configuration later. + +* Packages: ``Neos`` ``Media`` + +`TASK: Tweak dependency on neos/twitter-bootstrap `_ +------------------------------------------------------------------------------------------------------------------- + +- move the dependency from ``neos/neos`` to ``neos/media-browser`` +- change from ``*`` to ``^3.0.6`` (the first version allowing Neos 7.x) + + +* Packages: ``Neos`` ``Media.Browser`` + +`TASK: All dependencies within collection point to `self.version` `_ +----------------------------------------------------------------------------------------------------------------------------------- + +Re-adjusts dependencies to point to ``self.version`` for easier maintenance. + +* Fixes: `#4257 `_ + +* Packages: ``Neos`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/837.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/837.rst new file mode 100644 index 00000000000..7c6e7770528 --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/837.rst @@ -0,0 +1,258 @@ +`8.3.7 (2023-12-12) `_ +============================================================================================== + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Remove unnecessary basemixins dependency for ContentReferences `_ +----------------------------------------------------------------------------------------------------------------------------------------- + +The package has no direct dependency to the basemixins and should be usable without the other basemixins. + +* Packages: ``NodeTypes.ContentReferences`` + +`BUGFIX: Replace incorrect mention of itemRenderer to itemReducer `_ +----------------------------------------------------------------------------------------------------------------------------------- + +I'm not sure if we should target the 7.0 branch and then apply this to all following versions? + +Was introduced here: +https://github.com/neos/neos-development-collection/commit/`a7bc229541baa91dde6a85fbe4853b88db511af5 ``_#diff-``3ed794da2fb825c61dd55eee6535b77958799b33 `_384df7c47ac2a22242608969R207-R210 + +* Packages: ``Neos`` + +`BUGFIX: Add workspace hash to NodeDynamicTag_ and AssetDynamicTag_ cache tags `_ +------------------------------------------------------------------------------------------------------------------------------------------------ + +* Fixes: `#4781 `_ + +* Packages: ``Neos`` + +`BUGFIX: Filter for assets by asset collection without overriding existing WHERE conditions `_ +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +The query to fetch assets gets build in multiple steps. E.g in ``findAll`` it creates the query, adds the "variant filter clause" and afterwards the "asset collection filter clause". + +``` + public function findAll(AssetCollection $assetCollection = null): QueryResultInterface + { + $query = $this->createQuery(); + $this->addAssetVariantFilterClause($query); + $this->addAssetCollectionToQueryConstraints($query, $assetCollection); + return $query->execute(); + } +``` +But adding the "asset collection filter clause" removes/overrides the existing "variant filter clause" + +This fix replaces the way of setting "asset collection filter clause", so the existing where clauses are retained. + +* Fixes: `#4723 `_ + +* Packages: ``Media`` + +`BUGFIX: flashmessages in legacy modules work as expected `_ +--------------------------------------------------------------------------------------------------------------------------- + +This applies the, since a while unused, presets for message severity types to notifications in classic Neos backend modules again so that eg. warnings and errors stay on display and can be copied / screenshotted. + +Also regenerates js maps that seem to have compiled wrongly before. + +* Fixes: `#4672 `_ + +* Packages: ``Neos`` + +`BUGFIX: Check if image is possible to refit on replacement `_ +----------------------------------------------------------------------------------------------------------------------------- + +* Fixes: `#4752 `_ + +Checks if the replacement is an image and has a size. + +* Packages: ``Neos`` ``NodeTypes.BaseMixins`` ``Media`` + +`BUGFIX: Fix and make `nodeTypes:show` command usable `_ +----------------------------------------------------------------------------------------------------------------------- + +Fixes handling of invalid nodetype or invalid path. + +And introduces new option ``level`` to clamp the input (previously this command was useless for bigger nodetypes): + +``` +flow nodeTypes:show Neos.Demo:Document.Homepage --path properties --level 1 +NodeType configuration "Neos.Demo:Document.Homepage.properties": + +_removed: ... +_creationDateTime: ... +_lastModificationDateTime: ... +_lastPublicationDateTime: ... +_path: ... +_name: ... +_nodeType: ... +_hidden: ... +_hiddenBeforeDateTime: ... +_hiddenAfterDateTime: ... +titleOverride: ... +metaDescription: ... +... +``` + + +* Packages: ``Neos`` ``ContentRepository`` + +`BUGFIX: Allow disabling of auto-created Image Variants `_ +------------------------------------------------------------------------------------------------------------------------- + +Fixes support for the setting ``autoCreateImageVariantPresets`` that was documented for a long time but never actually evaluated. + +This change set: + +* Adjusts ``AssetService::assetCreated()`` signal to only trigger ``AssetVariantGenerator::createVariants()`` if the ``autoCreateImageVariantPresets`` flag is set +* Sets the default value of the flag to ``true`` for greater backwards compatibility +* Adjusts ``AssetVariantGenerator::createVariant()`` to only create a variant if it does not exist already – previously multiple variants with the same identifiers could be created for a single asset leading to undeterministic behavior +* Adds a button "Create missing Variants" to the ``Variants`` tab of the Media Module allowing editors to manually trigger creation of (missing) variants. + +* Fixes: `#4300 `_ + +* Packages: ``Neos`` ``Media`` + +`BUGFIX: `props` will be unset after an exception `_ +------------------------------------------------------------------------------------------------------------------- + +* Resolves: `#4525 `_ + +The rendering in a Neos.Fusion Component had a bug where the ``props`` might be undefined if an exception happened earlier in an eel expression. + +This was caused by not correctly poping the runtimes context and thus causing a unexpected shift in the context stack. + +**Upgrade instructions** + + +* Packages: ``Neos`` ``Fusion`` + +`BUGFIX: Reduce nodetype schema size `_ +------------------------------------------------------------------------------------------------------ + +With this change the following optimisations are done to improve speed and reduce size of the schema generation: + +* Abstract nodetypes are not queried anymore for constraints as they are already resolved by the nodetype manager. + +* Entries in the inheritance map and constraints will be skipped if they don’t contain any data. + +These optimisations reduce the size of the schema in the Neos.Demo from ~357KB to ~300KB and improve the response time by ~20% in my tests. + +The more nodetypes a project has, the bigger the benefit is. + +**Review instructions** + +Everything should work the same, adding nodes, constraints, etc. + + +* Packages: ``Neos`` + +`TASK: Use role label in list users/new user view if available `_ +-------------------------------------------------------------------------------------------------------------------------------- + +In Neos 7 (and with https://github.com/neos/flow-development-collection/issues/2162), role labels were introduced. While we now have a nice table view in the "edit account" view, the role label is not displayed anywhere else. + +I'm aware that Neos 7 and 8 are in maintenance-only mode, but I think we all agree that Neos 8 will be around for quite a while. I suggest the minimal change to use the role label in the user list and the "new user" view if there is one. + +- [N/A] Code follows the PSR-2 coding style +- [N/A] Tests have been created, run and adjusted as needed +- [x] The PR is created against the `lowest maintained branch `_ +- [x] Reviewer - PR Title is brief but complete and starts with ``FEATURE|TASK|BUGFIX`` +- [x] Reviewer - The first section explains the change briefly for change-logs +- [N/A] Reviewer - Breaking Changes are marked with ``!!!`` and have upgrade-instructions + +* Packages: ``Neos`` + +`TASK: Add PHP 8.3 to build workflow matrix `_ +------------------------------------------------------------------------------------------------------------- + +This will test Flow against PHP 8.3 + + +* Packages: ``Neos`` ``.github`` + +`TASK: Fusion Behat Test Make Sure To Throw Runtime Exceptions `_ +-------------------------------------------------------------------------------------------------------------------------------- + +Additional fix for https://github.com/neos/neos-development-collection/pull/4686 + +Instead of manually declaring ``@exceptionHandler`` we set it automatically for the entry point ;) This will help us detect exceptions which would otherwise be absorbed. + +Dont worry, this is only temporary and not at all needed with neos 9 + +**Upgrade instructions** + + +* Packages: ``Neos`` + +`TASK: Adjust neos/neos css build to work on apple silicon `_ +---------------------------------------------------------------------------------------------------------------------------- + + + +* Packages: ``Neos`` + +`TASK: Add tests for FlowQuery `nextUntil`, `prevUntil`, `siblings` and `find` `_ +------------------------------------------------------------------------------------------------------------------------------------------------ + +The tests of ``nextUntil``, ``prevUntil``, ``siblings`` and ``find`` that were still missing. + +**Upgrade instructions** + + +* Packages: ``Neos`` + +`TASK: Add behat tests for flowQuery cr operations `_ +-------------------------------------------------------------------------------------------------------------------- + +This should help to ensure that the behvior of flowQuery stays compatible with Neos 9. + + +* Packages: ``Neos`` + +`TASK: Add support information to package composer.json `_ +------------------------------------------------------------------------------------------------------------------------- + +The git url will be useful for automating the split configuration later. + +* Packages: ``Neos`` ``Media`` + +`TASK: 2nd fixup for PR #4641 ContentCollection.feature `_ +------------------------------------------------------------------------------------------------------------------------- + +see `#4641 `_ + +**Upgrade instructions** + + +* Packages: ``Neos`` + +`TASK: Tweak dependency on neos/twitter-bootstrap `_ +------------------------------------------------------------------------------------------------------------------- + +- move the dependency from ``neos/neos`` to ``neos/media-browser`` +- change from ``*`` to ``^3.0.6`` (the first version allowing Neos 7.x) + + +* Packages: ``Neos`` ``Media.Browser`` + +`TASK: All dependencies within collection point to `self.version` `_ +----------------------------------------------------------------------------------------------------------------------------------- + +Re-adjusts dependencies to point to ``self.version`` for easier maintenance. + +* Fixes: `#4257 `_ + +* Packages: ``Neos`` + +`TASK: Behat-based Fusion tests (v1) `_ +------------------------------------------------------------------------------------------------------ + +* Related: `#3594 `_ + +* Packages: ``Neos`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/Appendixes/ChangeLogs/838.rst b/Neos.Neos/Documentation/Appendixes/ChangeLogs/838.rst new file mode 100644 index 00000000000..8d6a41e01ea --- /dev/null +++ b/Neos.Neos/Documentation/Appendixes/ChangeLogs/838.rst @@ -0,0 +1,19 @@ +`8.3.8 (2023-12-13) `_ +============================================================================================== + +Overview of merged pull requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BUGFIX: Filter for assets by asset collection by using constraints `_ +------------------------------------------------------------------------------------------------------------------------------------- + +Fixes a bug, that was introduced with: https://github.com/neos/neos-development-collection/pull/4724 + +Because of using the QueryBuilder twice, the count of the paramter and for the query is not correct anymore. + +* Fixes: `#4801 `_ + +* Packages: ``Neos`` ``Media`` + +`Detailed log `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index fc0cfe20c15..0bbc4e7ce71 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,291 +19,218 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2017-05-11 +The following reference was automatically generated from code on 2024-01-12 -.. _`Neos Command Reference: NEOS.CONTENTREPOSITORY`: - -Package *NEOS.CONTENTREPOSITORY* --------------------------------- +.. _`Neos Command Reference: NEOS.FLOW`: +Package *NEOS.FLOW* +------------------- -.. _`Neos Command Reference: NEOS.CONTENTREPOSITORY neos.contentrepository:nodetypes:show`: -``neos.contentrepository:nodetypes:show`` -************************************** +.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:collectgarbage`: -**Show NodeType Configuration** +``neos.flow:cache:collectgarbage`` +********************************** -Shows the merged configuration (including supertypes) of a NodeType +**Cache Garbage Collection** -**Examples:** +Runs the Garbage Collection (collectGarbage) method on all registered caches. -``./flow nodeTypes:show Vendor.Site:Content`` +Though the method is defined in the BackendInterface, the implementation +can differ and might not remove any data, depending on possibilities of +the backend. -``./flow nodeTypes:show Vendor.Site:Content --path="properties.bar"`` Options ^^^^^^^ -``--node-type-name`` - The name of the NodeType to show -``--path`` - Optional path of the NodeType-configuration which will be shown +``--cache-identifier`` + If set, this command only applies to the given cache -.. _`Neos Command Reference: NEOS.CONTENTREPOSITORY neos.contentrepository:nodetypes:list`: -``neos.contentrepository:nodetypes:list`` -************************************** -**List NodeTypes** +.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:flush`: + +``neos.flow:cache:flush`` +************************* -Lists all declared NodeTypes grouped by namespace +**Flush all caches** -**Examples:** +The flush command flushes all caches (including code caches) which have been +registered with Flow's Cache Manager. It will NOT remove any session data, unless +you specifically configure the session caches to not be persistent. -``./flow nodeTypes:list --filter Vendor.Site:`` +If fatal errors caused by a package prevent the compile time bootstrap +from running, the removal of any temporary data can be forced by specifying +the option **--force**. -``./flow nodeTypes:list --filter Vendor.Site:Document --include-abstract`` +This command does not remove the precompiled data provided by frozen +packages unless the **--force** option is used. Options ^^^^^^^ -``--filter`` - Only NodeType-names containing this string will be listed -``--include-abstract`` - List abstract NodeTypes - - -.. _`Neos Command Reference: NEOS.CONTENTREPOSITORY neos.contentrepository:node:repair`: - -``neos.contentrepository:node:repair`` -************************************** - -**Repair inconsistent nodes** - -This command analyzes and repairs the node tree structure and individual nodes -based on the current node type configuration. - -It is possible to execute only one or more specific checks by providing the **--skip** -or **--only** option. See the full description of checks further below for possible check -identifiers. - -The following checks will be performed: - -*Remove abstract and undefined node types* -removeAbstractAndUndefinedNodes - -Will remove all nodes that has an abstract or undefined node type. - -*Remove orphan (parentless) nodes* -removeOrphanNodes - -Will remove all child nodes that do not have a connection to the root node. - -*Remove disallowed child nodes* -removeDisallowedChildNodes - -Will remove all child nodes that are disallowed according to the node type's auto-create -configuration and constraints. +``--force`` + Force flushing of any temporary data -*Remove undefined node properties* -removeUndefinedProperties -Will remove all undefined properties according to the node type configuration. -*Remove broken object references* -removeBrokenEntityReferences +Related commands +^^^^^^^^^^^^^^^^ -Detects and removes references from nodes to entities which don't exist anymore (for -example Image nodes referencing ImageVariant objects which are gone for some reason). +``neos.flow:cache:warmup`` + Warm up caches +``neos.flow:package:freeze`` + Freeze a package +``neos.flow:package:refreeze`` + Refreeze a package -*Remove nodes with invalid dimensions* -removeNodesWithInvalidDimensions -Will check for and optionally remove nodes which have dimension values not matching -the current content dimension configuration. -*Remove nodes with invalid workspace* -removeNodesWithInvalidWorkspace +.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:flushone`: -Will check for and optionally remove nodes which belong to a workspace which no longer -exists.. +``neos.flow:cache:flushone`` +**************************** -*Repair inconsistent node identifiers* -fixNodesWithInconsistentIdentifier +**Flushes a particular cache by its identifier** -Will check for and optionally repair node identifiers which are out of sync with their -corresponding nodes in a live workspace. +Given a cache identifier, this flushes just that one cache. To find +the cache identifiers, you can use the configuration:show command with +the type set to "Caches". -*Missing child nodes* -createMissingChildNodes +Note that this does not have a force-flush option since it's not +meant to remove temporary code data, resulting into a broken state if +code files lack. -For all nodes (or only those which match the --node-type filter specified with this -command) which currently don't have child nodes as configured by the node type's -configuration new child nodes will be created. +Arguments +^^^^^^^^^ -*Reorder child nodes* -reorderChildNodes +``--identifier`` + Cache identifier to flush cache for -For all nodes (or only those which match the --node-type filter specified with this -command) which have configured child nodes, those child nodes are reordered according to the -position from the parents NodeType configuration. -*Missing default properties* -addMissingDefaultValues -For all nodes (or only those which match the --node-type filter specified with this -command) which currently don\t have a property that have a default value configuration -the default value for that property will be set. -*Repair nodes with missing shadow nodes* -repairShadowNodes -This will reconstruct missing shadow nodes in case something went wrong in creating -or publishing them. This must be used on a workspace other than live. -It searches for nodes which have a corresponding node in one of the base workspaces, -have different node paths, but don't have a corresponding shadow node with a "movedto" -value. +Related commands +^^^^^^^^^^^^^^^^ -*Generate missing URI path segments* -generateUriPathSegments +``neos.flow:cache:flush`` + Flush all caches +``neos.flow:configuration:show`` + Show the active configuration settings -Generates URI path segment properties for all document nodes which don't have a path -segment set yet. -*Remove content dimensions from / and /sites* -removeContentDimensionsFromRootAndSitesNode -Removes content dimensions from the root and sites nodes +.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:list`: +``neos.flow:cache:list`` +************************ -**Examples:** +**List all configured caches and their status if available** -``./flow node:repair`` +This command will exit with a code 1 if at least one cache status contains errors or warnings +This allows the command to be easily integrated in CI setups (the --quiet flag can be used to reduce verbosity) -``./flow node:repair --node-type Neos.NodeTypes:Page`` -``./flow node:repair --workspace user-robert --only removeOrphanNodes,removeNodesWithInvalidDimensions`` -``./flow node:repair --skip removeUndefinedProperties`` +Options +^^^^^^^ +``--quiet`` + If set, this command only outputs errors & warnings -Options -^^^^^^^ -``--node-type`` - Node type name, if empty update all declared node types -``--workspace`` - Workspace name, default is 'live' -``--dry-run`` - Don't do anything, but report actions -``--cleanup`` - If FALSE, cleanup tasks are skipped -``--skip`` - Skip the given check or checks (comma separated) -``--only`` - Only execute the given check or checks (comma separated) +Related commands +^^^^^^^^^^^^^^^^ +``neos.flow:cache:show`` + Display details of a cache including a detailed status if available -.. _`Neos Command Reference: NEOS.CONTENTREPOSITORY.MIGRATION`: -Package *NEOS.CONTENTREPOSITORY.MIGRATION* -------------------- -.. _`Neos Command Reference: NEOS.CONTENTREPOSITORYREGISTRY neos.contentrepositoryregistry:nodemigration:migrationcreate`: +.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:setup`: -``neos.contentrepositoryregistry:nodemigration:migrationcreate`` +``neos.flow:cache:setup`` ************************* -**Create a node migration for the given package key** +**Setup the given Cache if possible** -You can specify the ``packageKey`` of your desired package. A node migration will be created in the specified package under ``/Migrations/ContentRepository/``. -The newly created node migration contains a small template to help you to get started, and also a link to the Neos documentation about how node migrations work in Neos. +Invokes the setup() method on the configured CacheBackend (if it implements the WithSetupInterface) +which should setup and validate the backend (i.e. create required database tables, directories, ...) Arguments ^^^^^^^^^ -``--package-key`` - The key for your package (for example ``Neos.Demo``) +``--cache-identifier`` + -Example -^^^^^^^ -.. code-block:: bash - ./flow node:migrationcreate --package-key Neos.Demo -.. _`Neos Command Reference: NEOS.FLOW`: -Package *NEOS.FLOW* -------------------- +Related commands +^^^^^^^^^^^^^^^^ -.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:flush`: +``neos.flow:cache:list`` + List all configured caches and their status if available +``neos.flow:cache:setupall`` + Setup all Caches -``neos.flow:cache:flush`` -************************* -**Flush all caches** -The flush command flushes all caches (including code caches) which have been -registered with Flow's Cache Manager. It also removes any session data. +.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:setupall`: -If fatal errors caused by a package prevent the compile time bootstrap -from running, the removal of any temporary data can be forced by specifying -the option **--force**. +``neos.flow:cache:setupall`` +**************************** -This command does not remove the precompiled data provided by frozen -packages unless the **--force** option is used. +**Setup all Caches** + +Invokes the setup() method on all configured CacheBackend that implement the WithSetupInterface interface +which should setup and validate the backend (i.e. create required database tables, directories, ...) + +This command will exit with a code 1 if at least one cache setup failed +This allows the command to be easily integrated in CI setups (the --quiet flag can be used to reduce verbosity) Options ^^^^^^^ -``--force`` - Force flushing of any temporary data +``--quiet`` + If set, this command only outputs errors & warnings Related commands ^^^^^^^^^^^^^^^^ -``neos.flow:cache:warmup`` - Warm up caches -``neos.flow:package:freeze`` - Freeze a package -``neos.flow:package:refreeze`` - Refreeze a package +``neos.flow:cache:setup`` + Setup the given Cache if possible -.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:flushone`: +.. _`Neos Command Reference: NEOS.FLOW neos.flow:cache:show`: -``neos.flow:cache:flushone`` -**************************** +``neos.flow:cache:show`` +************************ -**Flushes a particular cache by its identifier** +**Display details of a cache including a detailed status if available** -Given a cache identifier, this flushes just that one cache. To find -the cache identifiers, you can use the configuration:show command with -the type set to "Caches". -Note that this does not have a force-flush option since it's not -meant to remove temporary code data, resulting into a broken state if -code files lack. Arguments ^^^^^^^^^ -``--identifier`` - Cache identifier to flush cache for +``--cache-identifier`` + identifier of the cache (for example "Flow_Core") @@ -312,10 +239,8 @@ Arguments Related commands ^^^^^^^^^^^^^^^^ -``neos.flow:cache:flush`` - Flush all caches -``neos.flow:configuration:show`` - Show the active configuration settings +``neos.flow:cache:list`` + List all configured caches and their status if available @@ -396,7 +321,14 @@ Options The command shows the configuration of the current context as it is used by Flow itself. You can specify the configuration type and path if you want to show parts of the configuration. -./flow configuration:show --type Settings --path Neos.Flow.persistence +Display all settings: +./flow configuration:show + +Display Flow persistence settings: +./flow configuration:show --path Neos.Flow.persistence + +Display Flow Object Cache configuration +./flow configuration:show --type Caches --path Flow_Object_Classes @@ -404,7 +336,7 @@ Options ^^^^^^^ ``--type`` - Configuration type to show + Configuration type to show, defaults to Settings ``--path`` path to subconfiguration separated by "." like "Neos.Flow @@ -438,7 +370,7 @@ Options ``--path`` path to the subconfiguration separated by "." like "Neos.Flow ``--verbose`` - if TRUE, output more verbose information on the schema files which were used + if true, output more verbose information on the schema files which were used @@ -520,23 +452,6 @@ Arguments -.. _`Neos Command Reference: NEOS.FLOW neos.flow:core:shell`: - -``neos.flow:core:shell`` -************************ - -**Run the interactive Shell** - -The shell command runs Flow's interactive shell. This shell allows for -entering commands like through the regular command line interface but -additionally supports autocompletion and a user-based command history. - - - - - - - .. _`Neos Command Reference: NEOS.FLOW neos.flow:database:setcharset`: ``neos.flow:database:setcharset`` @@ -556,20 +471,19 @@ For background information on this, see: - http://stackoverflow.com/questions/766809/ - http://dev.mysql.com/doc/refman/5.5/en/alter-table.html +- https://medium.com/@adamhooper/in-mysql-never-use-utf8-use-utf8mb4-11761243e434 +- https://mathiasbynens.be/notes/mysql-utf8mb4 +- https://florian.ec/articles/mysql-doctrine-utf8/ -The main purpose of this is to fix setups that were created with Flow 2.3.x or earlier and whose -database server did not have a default collation of utf8mb4_unicode_ci. In those cases, the tables will -have a collation that does not match the default collation of later Flow versions, potentially leading -to problems when creating foreign key constraints (among others, potentially). +The main purpose of this is to fix setups that were created with Flow before version 5.0. In those cases, +the tables will have a collation that does not match the default collation of later Flow versions, potentially +leading to problems when creating foreign key constraints (among others, potentially). If you have special needs regarding the charset and collation, you *can* override the defaults with -different ones. One thing this might be useful for is when switching to the utf8mb4mb4 character set, see: - -- https://mathiasbynens.be/notes/mysql-utf8mb4 -- https://florian.ec/articles/mysql-doctrine-utf8/ +different ones. Note: This command **is not a general purpose conversion tool**. It will specifically not fix cases -of actual utf8mb4 stored in latin1 columns. For this a conversion to BLOB followed by a conversion to the +of actual utf8 stored in latin1 columns. For this a conversion to BLOB followed by a conversion to the proper type, charset and collation is needed instead. @@ -773,7 +687,7 @@ Related commands **Generate a new migration** -If $diffAgainstCurrent is TRUE (the default), it generates a migration file +If $diffAgainstCurrent is true (the default), it generates a migration file with the diff between current DB structure and the found mapping metadata. Otherwise an empty migration skeleton is generated. @@ -798,6 +712,8 @@ Options Whether to base the migration on the current schema structure ``--filter-expression`` Only include tables/sequences matching the filter expression regexp +``--force`` + Generate migrations even if there are migrations left to execute @@ -832,8 +748,6 @@ Options ``--show-migrations`` Output a list of all migrations and their status -``--show-descriptions`` - Show descriptions for the migrations (enables versions display) @@ -974,30 +888,18 @@ Options -.. _`Neos Command Reference: NEOS.FLOW neos.flow:package:activate`: - -``neos.flow:package:activate`` -****************************** - -**Activate an available package** - -This command activates an existing, but currently inactive package. +.. _`Neos Command Reference: NEOS.FLOW neos.flow:middleware:list`: -Arguments -^^^^^^^^^ +``neos.flow:middleware:list`` +***************************** -``--package-key`` - The package key of the package to create +**Lists all configured middleware components in the order they will be executed** -Related commands -^^^^^^^^^^^^^^^^ -``neos.flow:package:deactivate`` - Deactivate a package @@ -1035,54 +937,6 @@ Related commands -.. _`Neos Command Reference: NEOS.FLOW neos.flow:package:deactivate`: - -``neos.flow:package:deactivate`` -******************************** - -**Deactivate a package** - -This command deactivates a currently active package. - -Arguments -^^^^^^^^^ - -``--package-key`` - The package key of the package to create - - - - - -Related commands -^^^^^^^^^^^^^^^^ - -``neos.flow:package:activate`` - Activate an available package - - - -.. _`Neos Command Reference: NEOS.FLOW neos.flow:package:delete`: - -``neos.flow:package:delete`` -**************************** - -**Delete an existing package** - -This command deletes an existing package identified by the package key. - -Arguments -^^^^^^^^^ - -``--package-key`` - The package key of the package to create - - - - - - - .. _`Neos Command Reference: NEOS.FLOW neos.flow:package:freeze`: ``neos.flow:package:freeze`` @@ -1131,7 +985,7 @@ Related commands **List available packages** Lists all locally available packages. Displays the package key, version and -package title and its state – active or inactive. +package title. @@ -1143,14 +997,6 @@ Options -Related commands -^^^^^^^^^^^^^^^^ - -``neos.flow:package:activate`` - Activate an available package -``neos.flow:package:deactivate`` - Deactivate a package - .. _`Neos Command Reference: NEOS.FLOW neos.flow:package:refreeze`: @@ -1316,78 +1162,92 @@ Options -.. _`Neos Command Reference: NEOS.FLOW neos.flow:routing:getpath`: +.. _`Neos Command Reference: NEOS.FLOW neos.flow:routing:list`: -``neos.flow:routing:getpath`` -***************************** +``neos.flow:routing:list`` +************************** -**Generate a route path** +**List the known routes** -This command takes package, controller and action and displays the -generated route path and the selected route: +This command displays a list of all currently registered routes. -./flow routing:getPath --format json Acme.Demo\\Sub\\Package -Arguments -^^^^^^^^^ -``--package`` - Package key and subpackage, subpackage parts are separated with backslashes -Options -^^^^^^^ -``--controller`` - Controller name, default is 'Standard' -``--action`` - Action name, default is 'index' -``--format`` - Requested Format name default is 'html' +.. _`Neos Command Reference: NEOS.FLOW neos.flow:routing:match`: +``neos.flow:routing:match`` +*************************** +**Match the given URI to a corresponding route** +This command takes an incoming URI and displays the +matched Route and the mapped routing values (if any): +./flow routing:match "/de" --parameters="{\"requestUriHost\": \"localhost\"}" -.. _`Neos Command Reference: NEOS.FLOW neos.flow:routing:list`: +Arguments +^^^^^^^^^ -``neos.flow:routing:list`` -************************** +``--uri`` + The incoming route, absolute or relative -**List the known routes** -This command displays a list of all currently registered routes. +Options +^^^^^^^ + +``--method`` + The HTTP method to simulate (default is 'GET') +``--parameters`` + Route parameters as JSON string. Make sure to specify this option as described in the description in order to prevent parsing issues +.. _`Neos Command Reference: NEOS.FLOW neos.flow:routing:resolve`: -.. _`Neos Command Reference: NEOS.FLOW neos.flow:routing:routepath`: +``neos.flow:routing:resolve`` +***************************** -``neos.flow:routing:routepath`` -******************************* +**Build an URI for the given parameters** -**Route the given route path** +This command takes package, controller and action and displays the +resolved URI and which route matched (if any): -This command takes a given path and displays the detected route and -the selected package, controller and action. +./flow routing:resolve Some.Package --controller SomeController --additional-arguments="{\"some-argument\": \"some-value\"}" Arguments ^^^^^^^^^ -``--path`` - The route path to resolve +``--package`` + Package key (according to "@package" route value) Options ^^^^^^^ -``--method`` - The request method (GET, POST, PUT, DELETE, ...) to simulate +``--controller`` + Controller name (according to "@controller" route value), default is 'Standard' +``--action`` + Action name (according to "@action" route value), default is 'index' +``--format`` + Requested Format name (according to "@format" route value), default is 'html' +``--subpackage`` + SubPackage name (according to "@subpackage" route value) +``--additional-arguments`` + Additional route values as JSON string. Make sure to specify this option as described in the description in order to prevent parsing issues +``--parameters`` + Route parameters as JSON string. Make sure to specify this option as described in the description in order to prevent parsing issues +``--base-uri`` + Base URI of the simulated request, default ist 'http://localhost' +``--force-absolute-uri`` + Whether or not to force the creation of an absolute URI @@ -1414,12 +1274,12 @@ Arguments -.. _`Neos Command Reference: NEOS.FLOW neos.flow:security:generatekeypair`: +.. _`Neos Command Reference: NEOS.FLOW neos.flow:schema:validate`: -``neos.flow:security:generatekeypair`` -************************************** +``neos.flow:schema:validate`` +***************************** -**Generate a public/private key pair and add it to the RSAWalletService** +**Validate the given configurationfile againt a schema file** @@ -1428,34 +1288,80 @@ Arguments Options ^^^^^^^ -``--used-for-passwords`` - If the private key should be used for passwords +``--configuration-file`` + path to the validated configuration file +``--schema-file`` + path to the schema file +``--verbose`` + if true, output more verbose information on the schema files which were used -Related commands -^^^^^^^^^^^^^^^^ -``neos.flow:security:importprivatekey`` - Import a private key +.. _`Neos Command Reference: NEOS.FLOW neos.flow:security:describerole`: +``neos.flow:security:describerole`` +*********************************** -.. _`Neos Command Reference: NEOS.FLOW neos.flow:security:importprivatekey`: +**Show details of a specified role** -``neos.flow:security:importprivatekey`` -*************************************** -**Import a private key** -Read a PEM formatted private key from stdin and import it into the -RSAWalletService. The public key will be automatically extracted and stored -together with the private key as a key pair. +Arguments +^^^^^^^^^ -You can generate the same fingerprint returned from this using these commands: +``--role`` + identifier of the role to describe (for example "Neos.Flow:Everybody") -ssh-keygen -yf my-key.pem > my-key.pub -ssh-keygen -lf my-key.pub + + + + + + +.. _`Neos Command Reference: NEOS.FLOW neos.flow:security:generatekeypair`: + +``neos.flow:security:generatekeypair`` +************************************** + +**Generate a public/private key pair and add it to the RSAWalletService** + + + + + +Options +^^^^^^^ + +``--used-for-passwords`` + If the private key should be used for passwords + + + +Related commands +^^^^^^^^^^^^^^^^ + +``neos.flow:security:importprivatekey`` + Import a private key + + + +.. _`Neos Command Reference: NEOS.FLOW neos.flow:security:importprivatekey`: + +``neos.flow:security:importprivatekey`` +*************************************** + +**Import a private key** + +Read a PEM formatted private key from stdin and import it into the +RSAWalletService. The public key will be automatically extracted and stored +together with the private key as a key pair. + +You can generate the same fingerprint returned from this using these commands: + +ssh-keygen -yf my-key.pem > my-key.pub +ssh-keygen -lf my-key.pub To create a private key to import using this method, you can use: @@ -1508,6 +1414,27 @@ Related commands +.. _`Neos Command Reference: NEOS.FLOW neos.flow:security:listroles`: + +``neos.flow:security:listroles`` +******************************** + +**List all configured roles** + + + + + +Options +^^^^^^^ + +``--include-abstract`` + Set this flag to include abstract roles + + + + + .. _`Neos Command Reference: NEOS.FLOW neos.flow:security:showeffectivepolicy`: ``neos.flow:security:showeffectivepolicy`` @@ -1600,6 +1527,69 @@ Options +.. _`Neos Command Reference: NEOS.FLOW neos.flow:session:collectgarbage`: + +``neos.flow:session:collectgarbage`` +************************************ + +**Run garbage collection for sesions.** + +This command will remove session-data and -metadate of outdated sessions +identified by lastActivityTimestamp being older than inactivityTimeout + +!!! This is usually done automatically after shutdown for the percentage +of requests specified in the setting `Neos.Flow.session.garbageCollection.probability` + +Use this command if you need more direct control over the cleanup intervals. + + + + + + + +.. _`Neos Command Reference: NEOS.FLOW neos.flow:session:destroyall`: + +``neos.flow:session:destroyall`` +******************************** + +**Destroys all sessions.** + +This special command is needed, because sessions are kept in persistent storage and are not flushed +with other caches by default. + +This is functionally equivalent to +`./flow flow:cache:flushOne Flow_Session_Storage && ./flow flow:cache:flushOne Flow_Session_MetaData` + + + + + + + +.. _`Neos Command Reference: NEOS.FLOW neos.flow:signal:listconnected`: + +``neos.flow:signal:listconnected`` +********************************** + +**Lists all connected signals with their slots.** + + + + + +Options +^^^^^^^ + +``--class-name`` + if specified, only signals matching the given fully qualified class name will be shown. Note: escape namespace separators or wrap the value in quotes, e.g. "--class-name Neos\\Flow\\Core\\Bootstrap". +``--method-name`` + if specified, only signals matching the given method name will be shown. This is only useful in conjunction with the "--class-name" option. + + + + + .. _`Neos Command Reference: NEOS.FLOW neos.flow:typeconverter:list`: ``neos.flow:typeconverter:list`` @@ -1641,7 +1631,7 @@ Generates Schema documentation (XSD) for your ViewHelpers, preparing the file to be placed online and used by any XSD-aware editor. After creating the XSD file, reference it in your IDE and import the namespace in your Fluid template by adding the xmlns:* attribute(s): - + Arguments ^^^^^^^^^ @@ -1655,9 +1645,11 @@ Options ^^^^^^^ ``--xsd-namespace`` - Unique target namespace used in the XSD schema (for example "http://yourdomain.org/ns/viewhelpers"). Defaults to "http://typo3.org/ns/". + Unique target namespace used in the XSD schema (for example "http://yourdomain.org/ns/viewhelpers"). Defaults to "https://neos.io/ns/". ``--target-file`` File path and name of the generated XSD schema. If not specified the schema will be output to standard output. +``--xsd-domain`` + Domain used in the XSD schema (for example "http://yourdomain.org"). Defaults to "https://neos.io". @@ -1690,8 +1682,9 @@ exist. By using the --generate-related flag, a missing package, model or repository can be created alongside, avoiding such an error. By specifying the --generate-templates flag, this command will also create -matching Fluid templates for the actions created. This option can only be -used in combination with --generate-actions. +matching Fluid templates for the actions created. +Alternatively, by specifying the --generate-fusion flag, this command will +create matching Fusion files for the actions. The default behavior is to not overwrite any existing code. This can be overridden by specifying the --force flag. @@ -1713,8 +1706,10 @@ Options Also generate index, show, new, create, edit, update and delete actions. ``--generate-templates`` Also generate the templates for each action. +``--generate-fusion`` + If Fusion templates should be generated instead of Fluid. ``--generate-related`` - Also create the mentioned package, related model and repository if neccessary. + Also create the mentioned package, related model and repository if necessary. ``--force`` Overwrite any existing controller or template code. Regardless of this flag, the package, model and repository will never be overwritten. @@ -1842,13 +1837,19 @@ Arguments +Options +^^^^^^^ + +``--package-type`` + Optional package type, e.g. "neos-plugin + Related commands ^^^^^^^^^^^^^^^^ -``typo3.flow:package:create`` - *Command not available* +``neos.flow:package:create`` + Create a new package @@ -1887,6 +1888,35 @@ Related commands +.. _`Neos Command Reference: NEOS.KICKSTARTER neos.kickstarter:kickstart:translation`: + +``neos.kickstarter:kickstart:translation`` +****************************************** + +**Kickstart translation** + +Generates the translation files for the given package. + +Arguments +^^^^^^^^^ + +``--package-key`` + The package key of the package for the translation +``--source-language-key`` + The language key of the default language + + + +Options +^^^^^^^ + +``--target-language-keys`` + Comma separated language keys for the target translations + + + + + .. _`Neos Command Reference: NEOS.MEDIA`: Package *NEOS.MEDIA* @@ -1910,6 +1940,8 @@ Options ``--preset`` Preset name, if provided only thumbnails matching that preset are cleared +``--quiet`` + If set, only errors will be displayed. @@ -1925,7 +1957,7 @@ Options Creates thumbnail images based on the configured thumbnail presets. Optional ``preset`` parameter to only create thumbnails for a specific thumbnail preset configuration. -Additionally accepts a ``async`` parameter determining if the created thumbnails are generated when created. +Additionally, accepts a ``async`` parameter determining if the created thumbnails are generated when created. @@ -1936,6 +1968,8 @@ Options Preset name, if not provided thumbnails are created for all presets ``--async`` Asynchronous generation, if not provided the setting ``Neos.Media.asyncThumbnails`` is used +``--quiet`` + If set, only errors will be displayed. @@ -1959,6 +1993,92 @@ Options ``--simulate`` If set, this command will only tell what it would do instead of doing it right away +``--quiet`` + + + + + + +.. _`Neos Command Reference: NEOS.MEDIA neos.media:media:listvariantpresets`: + +``neos.media:media:listvariantpresets`` +*************************************** + +**List all configurations for your imageVariants.** + +Doesn't matter if configured under 'Neos.Media.variantPresets' or already deleted from this configuration. +This command will find every single one for you. + + + + + + + +.. _`Neos Command Reference: NEOS.MEDIA neos.media:media:removeunused`: + +``neos.media:media:removeunused`` +********************************* + +**Remove unused assets** + +This command iterates over all existing assets, checks their usage count and lists the assets which are not +reported as used by any AssetUsageStrategies. The unused assets can than be removed. + + + +Options +^^^^^^^ + +``--asset-source`` + If specified, only assets of this asset source are considered. For example "neos" or "my-asset-management-system +``--quiet`` + If set, only errors will be displayed. +``--assume-yes`` + If set, "yes" is assumed for the "shall I remove ..." dialogs +``--only-tags`` + Comma-separated list of asset tag labels, that should be taken into account +``--limit`` + Limit the result of unused assets displayed and removed for this run. +``--only-collections`` + Comma-separated list of asset collection titles, that should be taken into account + + + + + +.. _`Neos Command Reference: NEOS.MEDIA neos.media:media:removevariants`: + +``neos.media:media:removevariants`` +*********************************** + +**Cleanup imageVariants with provided identifier and variant name.** + +Image variants that are still configured are removed without usage check and +can be regenerated afterwards with `media:renderVariants`. + +This command will not remove any custom cropped image variants. + +Arguments +^^^^^^^^^ + +``--identifier`` + Identifier of variants to remove. +``--variant-name`` + Variants with this name will be removed (if exist). + + + +Options +^^^^^^^ + +``--quiet`` + If set, only errors and questions will be displayed. +``--assume-yes`` + If set, "yes" is assumed for the "shall I remove ..." dialog. +``--limit`` + Limit the result of unused assets displayed and removed for this run. @@ -1981,6 +2101,36 @@ Options ``--limit`` Limit the amount of thumbnails to be rendered to avoid memory exhaustion +``--quiet`` + If set, only errors will be displayed. + + + + + +.. _`Neos Command Reference: NEOS.MEDIA neos.media:media:rendervariants`: + +``neos.media:media:rendervariants`` +*********************************** + +**Render asset variants** + +Loops over missing configured asset variants and renders them. Optional ``limit`` parameter to +limit the amount of variants to be rendered to avoid memory exhaustion. + +If the re-render parameter is given, any existing variants will be rendered again, too. + + + +Options +^^^^^^^ + +``--limit`` + Limit the amount of variants to be rendered to avoid memory exhaustion +``--quiet`` + If set, only errors will be displayed. +``--recreate`` + If set, existing asset variants will be re-generated and replaced @@ -1992,12 +2142,70 @@ Package *NEOS.NEOS* ------------------- +.. _`Neos Command Reference: NEOS.NEOS neos.neos:cr:export`: + +``neos.neos:cr:export`` +*********************** + +**Export the events from the specified content repository** + + + +Arguments +^^^^^^^^^ + +``--path`` + The path for storing the result + + + +Options +^^^^^^^ + +``--content-repository`` + The content repository identifier +``--verbose`` + If set, all notices will be rendered + + + + + +.. _`Neos Command Reference: NEOS.NEOS neos.neos:cr:import`: + +``neos.neos:cr:import`` +*********************** + +**Import the events from the path into the specified content repository** + + + +Arguments +^^^^^^^^^ + +``--path`` + The path of the stored events like resource://Neos.Demo/Private/Content + + + +Options +^^^^^^^ + +``--content-repository`` + The content repository identifier +``--verbose`` + If set, all notices will be rendered + + + + + .. _`Neos Command Reference: NEOS.NEOS neos.neos:domain:activate`: ``neos.neos:domain:activate`` ***************************** -**Activate a domain record by hostname** +**Activate a domain record by hostname (with globbing)** @@ -2005,7 +2213,7 @@ Arguments ^^^^^^^^^ ``--hostname`` - The hostname to activate + The hostname to activate (globbing is supported) @@ -2026,7 +2234,7 @@ Arguments ^^^^^^^^^ ``--site-node-name`` - The nodeName of the site rootNode, e.g. "neostypo3org + The nodeName of the site rootNode, e.g. "flowneosio ``--hostname`` The hostname to match on, e.g. "flow.neos.io @@ -2049,7 +2257,7 @@ Options ``neos.neos:domain:deactivate`` ******************************* -**Deactivate a domain record by hostname** +**Deactivate a domain record by hostname (with globbing)** @@ -2057,7 +2265,7 @@ Arguments ^^^^^^^^^ ``--hostname`` - The hostname to deactivate + The hostname to deactivate (globbing is supported) @@ -2070,7 +2278,7 @@ Arguments ``neos.neos:domain:delete`` *************************** -**Delete a domain record by hostname** +**Delete a domain record by hostname (with globbing)** @@ -2078,7 +2286,7 @@ Arguments ^^^^^^^^^ ``--hostname`` - The hostname to remove + The hostname to remove (globbing is supported) @@ -2112,7 +2320,7 @@ Options ``neos.neos:site:activate`` *************************** -**Activate a site** +**Activate a site (with globbing)** This command activates the specified site. @@ -2120,7 +2328,7 @@ Arguments ^^^^^^^^^ ``--site-node`` - The node name of the site to activate + The node name of the sites to activate (globbing is supported) @@ -2138,13 +2346,13 @@ Arguments This command allows to create a blank site with just a single empty document in the default dimension. The name of the site, the packageKey must be specified. -If no ``nodeType`` option is specified the command will use `Neos.NodeTypes:Page` as fallback. The node type -must already exists and have the superType ``Neos.Neos:Document``. +The node type given with the ``nodeType`` option must already exists +and have the superType ``Neos.Neos:Document``. -If no ``nodeName` option is specified the command will create a unique node-name from the name of the site. +If no ``nodeName`` option is specified the command will create a unique node-name from the name of the site. If a node name is given it has to be unique for the setup. -If the flag ``activate` is set to false new site will not be activated. +If the flag ``activate`` is set to false new site will not be activated. Arguments ^^^^^^^^^ @@ -2153,18 +2361,18 @@ Arguments The name of the site ``--package-key`` The site package +``--node-type`` + The node type to use for the site node, e.g. Amce.Com:Page Options ^^^^^^^ -``--node-type`` - The node type to use for the site node. (Default = Neos.NodeTypes:Page) ``--node-name`` - The name of the site node. If no nodeName is given it will be determined from the siteName. + The name of the site node. ``--inactive`` - The new site is not activated immediately (default = false). + The new site is not activated immediately (default = false) @@ -2175,7 +2383,7 @@ Options ``neos.neos:site:deactivate`` ***************************** -**Deactivate a site** +**Deactivate a site (with globbing)** This command deactivates the specified site. @@ -2183,77 +2391,9 @@ Arguments ^^^^^^^^^ ``--site-node`` - The node name of the site to deactivate - - - - - - - -.. _`Neos Command Reference: NEOS.NEOS neos.neos:site:export`: - -``neos.neos:site:export`` -************************* - -**Export sites content (e.g. site:export --package-key "Neos.Demo")** - -This command exports all or one specific site with all its content into an XML format. - -If the package key option is given, the site(s) will be exported to the given package in the default -location Resources/Private/Content/Sites.xml. - -If the filename option is given, any resources will be exported to files in a folder named "Resources" -alongside the XML file. - -If neither the filename nor the package key option are given, the XML will be printed to standard output and -assets will be embedded into the XML in base64 encoded form. - - - -Options -^^^^^^^ - -``--site-node`` - the node name of the site to be exported; if none given will export all sites -``--tidy`` - Whether to export formatted XML. This is defaults to true -``--filename`` - relative path and filename to the XML file to create. Any resource will be stored in a sub folder "Resources". -``--package-key`` - Package to store the XML file in. Any resource will be stored in a sub folder "Resources". -``--node-type-filter`` - Filter the node type of the nodes, allows complex expressions (e.g. "Neos.Neos:Page", "!Neos.Neos:Page,Neos.Neos:Text") - - - + The node name of the sites to deactivate (globbing is supported) -.. _`Neos Command Reference: NEOS.NEOS neos.neos:site:import`: - -``neos.neos:site:import`` -************************* - -**Import sites content** - -This command allows for importing one or more sites or partial content from an XML source. The format must -be identical to that produced by the export command. - -If a filename is specified, this command expects the corresponding file to contain the XML structure. The -filename php://stdin can be used to read from standard input. - -If a package key is specified, this command expects a Sites.xml file to be located in the private resources -directory of the given package (Resources/Private/Content/Sites.xml). - - - -Options -^^^^^^^ - -``--package-key`` - Package key specifying the package containing the sites content -``--filename`` - relative path and filename to the XML file containing the sites content @@ -2279,17 +2419,17 @@ Options ``neos.neos:site:prune`` ************************ -**Remove all content and related data - for now. In the future we need some more sophisticated cleanup.** - +**Remove site with content and related data (with globbing)** +In the future we need some more sophisticated cleanup. +Arguments +^^^^^^^^^ +``--site-node`` + Name for site root nodes to clear only content of this sites (globbing is supported) -Options -^^^^^^^ -``--site-node`` - Name of a site root node to clear only content of this site. @@ -2300,7 +2440,7 @@ Options ``neos.neos:user:activate`` *************************** -**Activate a user** +**Activate a user (with globbing)** This command reactivates possibly expired accounts for the given user. @@ -2312,7 +2452,7 @@ Arguments ^^^^^^^^^ ``--username`` - The username of the user to be activated. + The username of the user to be activated (globbing is supported) @@ -2320,7 +2460,7 @@ Options ^^^^^^^ ``--authentication-provider`` - Name of the authentication provider to use for finding the user. Example: "Neos.Neos:Backend + Name of the authentication provider to use for finding the user. @@ -2346,7 +2486,7 @@ Arguments ^^^^^^^^^ ``--username`` - The username of the user + The username of the user (globbing is supported) ``--role`` Role to be added to the user, for example "Neos.Neos:Administrator" or just "Administrator @@ -2384,7 +2524,7 @@ Arguments ^^^^^^^^^ ``--username`` - The username of the user to be created, used as an account identifier for the newly created account + The username of the user to be created, ``--password`` Password of the user to be created ``--first-name`` @@ -2400,7 +2540,7 @@ Options ``--roles`` A comma separated list of roles to assign. Examples: "Editor, Acme.Foo:Reviewer ``--authentication-provider`` - Name of the authentication provider to use for the new account. Example: "Neos.Neos:Backend + Name of the authentication provider to use for the new account. @@ -2411,19 +2551,19 @@ Options ``neos.neos:user:deactivate`` ***************************** -**Deactivate a user** +**Deactivate a user (with globbing)** This command deactivates a user by flagging all of its accounts as expired. -If an authentication provider is specified, this command will look for an account with the given username related -to the given provider. Still, this command will deactivate **all** accounts of a user, once such a user has been -found. +If an authentication provider is specified, this command will look for an account with the given username +related to the given provider. Still, this command will deactivate **all** accounts of a user, +once such a user has been found. Arguments ^^^^^^^^^ ``--username`` - The username of the user to be deactivated. + The username of the user to be deactivated (globbing is supported) @@ -2431,7 +2571,7 @@ Options ^^^^^^^ ``--authentication-provider`` - Name of the authentication provider to use for finding the user. Example: "Neos.Neos:Backend + Name of the authentication provider to use for finding the user. @@ -2442,7 +2582,7 @@ Options ``neos.neos:user:delete`` ************************* -**Delete a user** +**Delete a user (with globbing)** This command deletes an existing Neos user. All content and data directly related to this user, including but not limited to draft workspace contents, will be removed as well. @@ -2458,7 +2598,7 @@ Arguments ^^^^^^^^^ ``--username`` - The username of the user to be removed + The username of the user to be removed (globbing is supported) @@ -2506,9 +2646,9 @@ Arguments ^^^^^^^^^ ``--username`` - The username of the user + The username of the user (globbing is supported) ``--role`` - Role to be removed from the user, for example "Neos.Neos:Administrator" or just "Administrator + Role to be removed from the user, @@ -2549,7 +2689,7 @@ Options ^^^^^^^ ``--authentication-provider`` - Name of the authentication provider to use for finding the user. Example: "Neos.Neos:Backend + Name of the authentication provider to use for finding the user. @@ -2573,7 +2713,7 @@ Arguments ^^^^^^^^^ ``--username`` - The username of the user to show. Usually refers to the account identifier of the user's Neos backend account. + The username of the user to show. @@ -2615,6 +2755,35 @@ Options A description explaining the purpose of the new workspace ``--owner`` The identifier of a User to own the workspace +``--content-repository-identifier`` + + + + + + +.. _`Neos Command Reference: NEOS.NEOS neos.neos:workspace:createroot`: + +``neos.neos:workspace:createroot`` +********************************** + +**Create a new root workspace for a content repository.** + + + +Arguments +^^^^^^^^^ + +``--name`` + + + + +Options +^^^^^^^ + +``--content-repository-identifier`` + @@ -2643,6 +2812,8 @@ Options ``--force`` Delete the workspace and all of its contents +``--content-repository-identifier`` + contentRepositoryIdentifier @@ -2674,45 +2845,10 @@ Arguments Options ^^^^^^^ -``--verbose`` - If enabled, information about individual nodes will be displayed -``--dry-run`` - If set, only displays which nodes would be discarded, no real changes are committed - - - - - -.. _`Neos Command Reference: NEOS.NEOS neos.neos:workspace:discardall`: - -``neos.neos:workspace:discardall`` -********************************** - -**Discard changes in workspace <b>(DEPRECATED)</b>** - -This command discards all modified, created or deleted nodes in the specified workspace. - -Arguments -^^^^^^^^^ - -``--workspace-name`` - Name of the workspace, for example "user-john - - - -Options -^^^^^^^ - -``--verbose`` - If enabled, information about individual nodes will be displayed - - +``--content-repository-identifier`` + -Related commands -^^^^^^^^^^^^^^^^ -``neos.neos:workspace:discard`` - Discard changes in workspace @@ -2727,6 +2863,12 @@ Related commands +Options +^^^^^^^ + +``--content-repository-identifier`` + contentRepositoryIdentifier + @@ -2739,7 +2881,6 @@ Related commands **Publish changes of a workspace** This command publishes all modified, created or deleted nodes in the specified workspace to its base workspace. -If a target workspace is specified, the content is published to that workspace instead. Arguments ^^^^^^^^^ @@ -2752,70 +2893,29 @@ Arguments Options ^^^^^^^ -``--target-workspace`` - If specified, the content will be published to this workspace instead of the base workspace -``--verbose`` - If enabled, some information about individual nodes will be displayed -``--dry-run`` - If set, only displays which nodes would be published, no real changes are committed - +``--content-repository-identifier`` + -.. _`Neos Command Reference: NEOS.NEOS neos.neos:workspace:publishall`: -``neos.neos:workspace:publishall`` -********************************** +.. _`Neos Command Reference: NEOS.NEOS neos.neos:workspace:rebaseoutdated`: -**Publish changes of a workspace <b>(DEPRECATED)</b>** +``neos.neos:workspace:rebaseoutdated`` +************************************** -This command publishes all modified, created or deleted nodes in the specified workspace to the live workspace. +**Rebase all outdated content streams** -Arguments -^^^^^^^^^ -``--workspace-name`` - Name of the workspace, for example "user-john Options ^^^^^^^ -``--verbose`` - If enabled, information about individual nodes will be displayed - - - -Related commands -^^^^^^^^^^^^^^^^ - -``neos.neos:workspace:publish`` - Publish changes of a workspace - - - -.. _`Neos Command Reference: NEOS.NEOS neos.neos:workspace:rebase`: - -``neos.neos:workspace:rebase`` -****************************** - -**Rebase a workspace** - -This command sets a new base workspace for the specified workspace. Note that doing so will put the possible -changes contained in the workspace to be rebased into a different context and thus might lead to unintended -results when being published. - -Arguments -^^^^^^^^^ - -``--workspace`` - Name of the workspace to rebase, for example "user-john -``--base-workspace`` - Name of the new base workspace - - +``--content-repository-identifier`` + contentRepositoryIdentifier diff --git a/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst b/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst index 60cf23d488f..ae77b43474b 100644 --- a/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst +++ b/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst @@ -3,7 +3,7 @@ FlowQuery Operation Reference ============================= -This reference was automatically generated from code on 2023-06-01 +This reference was automatically generated from code on 2024-01-12 .. _`FlowQuery Operation Reference: add`: @@ -24,27 +24,59 @@ or an Object. -.. _`FlowQuery Operation Reference: cacheLifetime`: +.. _`FlowQuery Operation Reference: backReferenceNodes`: -cacheLifetime -------------- +backReferenceNodes +------------------ -"cacheLifetime" operation working on ContentRepository nodes. Will get the minimum of all allowed cache lifetimes for the -nodes in the current FlowQuery context. This means it will evaluate to the nearest future value of the -hiddenBeforeDateTime or hiddenAfterDateTime properties of all nodes in the context. If none are set or all values -are in the past it will evaluate to NULL. +"backReferenceNodes" operation working on Nodes -To include already hidden nodes (with a hiddenBeforeDateTime value in the future) in the result, also invisible nodes -have to be included in the context. This can be achieved using the "context" operation before fetching child nodes. +This operation can be used to find the nodes that are referencing a given node: -Example: + ${q(node).backReferenceNodes().get()} - q(node).context({'invisibleContentShown': true}).children().cacheLifetime() +A referenceName can be specified as argument -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\CacheLifetimeOperation -:Priority: 1 -:Final: Yes -:Returns: integer The cache lifetime in seconds or NULL if either no content collection was given or no child node had a "hiddenBeforeDateTime" or "hiddenAfterDateTime" property set + ${q(node).backReferenceNodes("someReferenceName")} + + + +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\BackReferenceNodesOperation +:Priority: 100 +:Final: No +:Returns: void + + + + + +.. _`FlowQuery Operation Reference: backReferences`: + +backReferences +-------------- + +"backReferences" operation working on Nodes + +This operation can be used to find incoming references of a given node: + + ${q(node).backReferences().get()} + +The result is an array of {@see Reference} instances. + +To render the reference name of the first match: + + $q{node).backReferences().get(0).name.value} + +The {@see ReferencePropertyOperation} can be used to access any property on the reference relation: + + ${q(node).backReferences("someReferenceName").property("somePropertyName")} + + + +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\BackReferencesOperation +:Priority: 100 +:Final: No +:Returns: void @@ -59,7 +91,7 @@ children context elements and returns all child nodes or only those matching the filter expression specified as optional argument. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\ChildrenOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\ChildrenOperation :Priority: 100 :Final: No :Returns: void @@ -96,7 +128,7 @@ closest get the first node that matches the selector by testing the node itself and traversing up through its ancestors. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\ClosestOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\ClosestOperation :Priority: 100 :Final: No :Returns: void @@ -105,29 +137,6 @@ traversing up through its ancestors. -.. _`FlowQuery Operation Reference: context`: - -context -------- - -"context" operation working on ContentRepository nodes. Modifies the ContentRepository Context of each -node in the current FlowQuery context by the given properties and returns the same -nodes by identifier if they can be accessed in the new Context (otherwise they -will be skipped). - -Example: - - q(node).context({'invisibleContentShown': true}).children() - -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\ContextOperation -:Priority: 1 -:Final: No -:Returns: void - - - - - .. _`FlowQuery Operation Reference: count`: count @@ -214,18 +223,18 @@ filter This filter implementation contains specific behavior for use on ContentRepository nodes. It will not evaluate any elements that are not instances of the -`NodeInterface`. +`Node`. The implementation changes the behavior of the `instanceof` operator to work on node types instead of PHP object types, so that:: - [instanceof Acme.Com:Page] + [instanceof Neos.NodeTypes:Page] will in fact use `isOfType()` on the `NodeType` of context elements to filter. This filter allow also to filter the current context by a given node. Anything else remains unchanged. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\FilterOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\FilterOperation :Priority: 100 :Final: No :Returns: void @@ -244,37 +253,37 @@ of nodes specified by a path, identifier or node type (recursive). Example (node name): - q(node).find('main') + q(node).find('main') Example (relative path): - q(node).find('main/text1') + q(node).find('main/text1') Example (absolute path): - q(node).find('/sites/my-site/home') + q(node).find('/sites/my-site/home') Example (identifier): - q(node).find('#30e893c1-caef-0ca5-b53d-e5699bb8e506') + q(node).find('#30e893c1-caef-0ca5-b53d-e5699bb8e506') Example (node type): - q(node).find('[instanceof Acme.Com:Text]') + q(node).find('[instanceof Neos.NodeTypes:Text]') Example (multiple node types): - q(node).find('[instanceof Acme.Com:Text],[instanceof Acme.Com:Image]') + q(node).find('[instanceof Neos.NodeTypes:Text],[instanceof Neos.NodeTypes:Image]') Example (node type with filter): - q(node).find('[instanceof Acme.Com:Text][text*="Neos"]') + q(node).find('[instanceof Neos.NodeTypes:Text][text*="Neos"]') This operation operates rather on the given Context object than on the given node and thus may work with the legacy node interface until subgraphs are available {@inheritdoc} -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\FindOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\FindOperation :Priority: 100 :Final: No :Returns: void @@ -328,13 +337,13 @@ returned. If no such index exists, NULL is returned. has --- -"has" operation working on NodeInterface. Reduce the set of matched elements +"has" operation working on Node. Reduce the set of matched elements to those that have a child node that matches the selector or given subject. Accepts a selector, an array, an object, a traversable object & a FlowQuery object as argument. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\HasOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\HasOperation :Priority: 100 :Final: No :Returns: void @@ -383,10 +392,10 @@ Get the last element inside the context. neosUiDefaultNodes ------------------ -Fetches all nodes needed for the given state of the UI + :Implementation: Neos\\Neos\\Ui\\FlowQueryOperations\\NeosUiDefaultNodesOperation -:Priority: 100 +:Priority: 110 :Final: No :Returns: void @@ -404,7 +413,7 @@ context elements and returns all child nodes or only those matching the filter expression specified as optional argument. :Implementation: Neos\\Neos\\Ui\\FlowQueryOperations\\NeosUiFilteredChildrenOperation -:Priority: 100 +:Priority: 500 :Final: No :Returns: void @@ -422,7 +431,7 @@ context elements and returns the immediately following sibling. If an optional filter expression is provided, it only returns the node if it matches the given expression. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\NextOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\NextOperation :Priority: 100 :Final: No :Returns: void @@ -440,8 +449,8 @@ nextAll context elements and returns each following sibling or only those matching the filter expression specified as optional argument. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\NextAllOperation -:Priority: 0 +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\NextAllOperation +:Priority: 100 :Final: No :Returns: void @@ -459,8 +468,8 @@ and returns each following sibling until the matching sibling is found. If an optional filter expression is provided as a second argument, it only returns the nodes matching the given expression. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\NextUntilOperation -:Priority: 0 +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\NextUntilOperation +:Priority: 100 :Final: No :Returns: void @@ -468,17 +477,18 @@ it only returns the nodes matching the given expression. -.. _`FlowQuery Operation Reference: parent`: +.. _`FlowQuery Operation Reference: nextUntil`: -parent ------- +nextUntil +--------- -"parent" operation working on ContentRepository nodes. It iterates over all -context elements and returns each direct parent nodes or only those matching -the filter expression specified as optional argument. +"prevUntil" operation working on ContentRepository nodes. It iterates over all context elements +and returns each preceding sibling until the matching sibling is found. +If an optional filter expression is provided as a second argument, +it only returns the nodes matching the given expression. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\ParentOperation -:Priority: 100 +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\PrevUntilOperation +:Priority: 0 :Final: No :Returns: void @@ -486,17 +496,17 @@ the filter expression specified as optional argument. -.. _`FlowQuery Operation Reference: parents`: +.. _`FlowQuery Operation Reference: parent`: -parents -------- +parent +------ -"parents" operation working on ContentRepository nodes. It iterates over all -context elements and returns the parent nodes or only those matching +"parent" operation working on ContentRepository nodes. It iterates over all +context elements and returns each direct parent nodes or only those matching the filter expression specified as optional argument. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\ParentsOperation -:Priority: 0 +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\ParentOperation +:Priority: 100 :Final: No :Returns: void @@ -513,7 +523,7 @@ parents context elements and returns the parent nodes or only those matching the filter expression specified as optional argument. -:Implementation: Neos\\Neos\\Eel\\FlowQueryOperations\\ParentsOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\ParentsOperation :Priority: 100 :Final: No :Returns: void @@ -532,26 +542,7 @@ context elements and returns the parent nodes until the matching parent is found If an optional filter expression is provided as a second argument, it only returns the nodes matching the given expression. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\ParentsUntilOperation -:Priority: 0 -:Final: No -:Returns: void - - - - - -.. _`FlowQuery Operation Reference: parentsUntil`: - -parentsUntil ------------- - -"parentsUntil" operation working on ContentRepository nodes. It iterates over all -context elements and returns the parent nodes until the matching parent is found. -If an optional filter expression is provided as a second argument, -it only returns the nodes matching the given expression. - -:Implementation: Neos\\Neos\\Eel\\FlowQueryOperations\\ParentsUntilOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\ParentsUntilOperation :Priority: 100 :Final: No :Returns: void @@ -570,7 +561,7 @@ context elements and returns the immediately preceding sibling. If an optional filter expression is provided, it only returns the node if it matches the given expression. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\PrevOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\PrevOperation :Priority: 100 :Final: No :Returns: void @@ -588,26 +579,7 @@ prevAll context elements and returns each preceding sibling or only those matching the filter expression specified as optional argument -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\PrevAllOperation -:Priority: 0 -:Final: No -:Returns: void - - - - - -.. _`FlowQuery Operation Reference: prevUntil`: - -prevUntil ---------- - -"prevUntil" operation working on ContentRepository nodes. It iterates over all context elements -and returns each preceding sibling until the matching sibling is found. -If an optional filter expression is provided as a second argument, -it only returns the nodes matching the given expression. - -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\PrevUntilOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\PrevAllOperation :Priority: 0 :Final: No :Returns: void @@ -625,7 +597,7 @@ Used to access properties of a ContentRepository Node. If the property mame is prefixed with _, internal node properties like start time, end time, hidden are accessed. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\PropertyOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\PropertyOperation :Priority: 100 :Final: Yes :Returns: mixed @@ -654,6 +626,111 @@ element is returned. +.. _`FlowQuery Operation Reference: referenceNodes`: + +referenceNodes +-------------- + +"referenceNodes" operation working on Nodes + +This operation can be used to find the nodes that are referenced from a given node: + + ${q(node).referenceNodes().get()} + +If a referenceName is given as argument only the references for this name are returned + + ${q(node).referenceNodes("someReferenceName").} + + + +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\ReferenceNodesOperation +:Priority: 100 +:Final: No +:Returns: void + + + + + +.. _`FlowQuery Operation Reference: referenceProperty`: + +referenceProperty +----------------- + +Used to access properties of a ContentRepository Reference + +This operation can be used to return the value of a node reference: + + ${q(node).references("someReferenceName").referenceProperty("somePropertyName")} + + + +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\ReferencePropertyOperation +:Priority: 100 +:Final: Yes +:Returns: mixed + + + + + +.. _`FlowQuery Operation Reference: references`: + +references +---------- + +"references" operation working on Nodes + +This operation can be used to find outgoing references for a given node: + + ${q(node).references().get()} + +The result is an array of {@see Reference} instances. + +To render the reference name of the first match: + + $q{node).references().get(0).name.value} + +The {@see ReferencePropertyOperation} can be used to access any property on the reference relation: + + ${q(node).references("someReferenceName").property("somePropertyName")} + + + +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\ReferencesOperation +:Priority: 100 +:Final: No +:Returns: void + + + + + +.. _`FlowQuery Operation Reference: remove`: + +remove +------ + +Removes the given Node from the current context. + +The operation accepts one argument that may be an Array, a FlowQuery +or an Object. + +!!! This is a Node specific implementation of the generic `remove` operation!!! + +The result is an array of {@see Node} instances. + + + +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\RemoveOperation +:Priority: 100 +:Final: No +:Returns: void + + + + + .. _`FlowQuery Operation Reference: remove`: remove @@ -677,7 +754,9 @@ or an Object. search ------ +Custom search operation using the Content Graph fulltext search +Original implementation: \Neos\Neos\Ui\FlowQueryOperations\SearchOperation :Implementation: Neos\\Neos\\Ui\\FlowQueryOperations\\SearchOperation :Priority: 100 @@ -697,7 +776,7 @@ siblings context elements and returns all sibling nodes or only those matching the filter expression specified as optional argument. -:Implementation: Neos\\ContentRepository\\Eel\\FlowQueryOperations\\SiblingsOperation +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\SiblingsOperation :Priority: 100 :Final: No :Returns: void @@ -745,7 +824,8 @@ Third optional argument are the sort options (see https://www.php.net/manual/en/ - 'SORT_NATURAL' - 'SORT_FLAG_CASE' (use as last option with SORT_STRING, SORT_LOCALE_STRING or SORT_NATURAL) A single sort option can be supplied as string. Multiple sort options are supplied as array. -Other than the above listed sort options throw an error. Omitting the third parameter leaves FlowQuery sort() in SORT_REGULAR sort mode. +Other than the above listed sort options throw an error. +Omitting the third parameter leaves FlowQuery sort() in SORT_REGULAR sort mode. Example usages: sort("title", "ASC", ["SORT_NATURAL", "SORT_FLAG_CASE"]) sort("risk", "DESC", "SORT_NUMERIC") @@ -758,3 +838,45 @@ Example usages: + +.. _`FlowQuery Operation Reference: unique`: + +unique +------ + +"unique" operation working on Nodes + +This operation can be used to ensure that nodes are only once in the flow query context + + ${q(node).backReferences().nodes().unique()get()} + +The result is an array of {@see Node} instances. + +!!! This is a Node specific implementation of the generic `unique` operation!!! + + + +:Implementation: Neos\\ContentRepository\\NodeAccess\\FlowQueryOperations\\UniqueOperation +:Priority: 100 +:Final: No +:Returns: void + + + + + +.. _`FlowQuery Operation Reference: unique`: + +unique +------ + +Removes duplicate items from the current context. + +:Implementation: Neos\\Eel\\FlowQuery\\Operations\\UniqueOperation +:Priority: 1 +:Final: No +:Returns: void + + + + diff --git a/Neos.Neos/Documentation/References/NeosFusionReference.rst b/Neos.Neos/Documentation/References/NeosFusionReference.rst index b0873b08bae..0f37f4b02f6 100644 --- a/Neos.Neos/Documentation/References/NeosFusionReference.rst +++ b/Neos.Neos/Documentation/References/NeosFusionReference.rst @@ -146,7 +146,7 @@ Render each item in ``items`` using ``itemRenderer`` and return the result as an Neos.Fusion:Reduce ------------------ -Reduce the given items to a single value by using ``itemRenderer``. +Reduce the given items to a single value by using ``itemReducer``. :items: (array/Iterable, **required**) The array or iterable to iterate over (to calculate ``iterator.isLast`` items have to be ``countable``) :itemName: (string, defaults to ``item``) Context variable name for each item diff --git a/Neos.Neos/Documentation/References/Signals/ContentRepository.rst b/Neos.Neos/Documentation/References/Signals/ContentRepository.rst index e358879b510..9d9bafbf4fa 100644 --- a/Neos.Neos/Documentation/References/Signals/ContentRepository.rst +++ b/Neos.Neos/Documentation/References/Signals/ContentRepository.rst @@ -3,244 +3,5 @@ Content Repository Signals Reference ==================================== -This reference was automatically generated from code on 2023-06-01 - - -.. _`Content Repository Signals Reference: Context (``Neos\ContentRepository\Domain\Service\Context``)`: - -Context (``Neos\ContentRepository\Domain\Service\Context``) ------------------------------------------------------------ - -This class contains the following signals. - -beforeAdoptNode -^^^^^^^^^^^^^^^ - - - -afterAdoptNode -^^^^^^^^^^^^^^ - - - - - - - - -.. _`Content Repository Signals Reference: Node (``Neos\ContentRepository\Domain\Model\Node``)`: - -Node (``Neos\ContentRepository\Domain\Model\Node``) ---------------------------------------------------- - -This class contains the following signals. - -beforeNodeMove -^^^^^^^^^^^^^^ - - - -afterNodeMove -^^^^^^^^^^^^^ - - - -beforeNodeCopy -^^^^^^^^^^^^^^ - - - -afterNodeCopy -^^^^^^^^^^^^^ - - - -nodePathChanged -^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that the node path has been changed. - -beforeNodeCreate -^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node will be created. - -afterNodeCreate -^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node was created. - -nodeAdded -^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node was added. - -nodeUpdated -^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node was updated. - -nodeRemoved -^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node was removed. - -beforeNodePropertyChange -^^^^^^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that the property of a node will be changed. - -nodePropertyChanged -^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that the property of a node was changed. - - - - - - -.. _`Content Repository Signals Reference: NodeData (``Neos\ContentRepository\Domain\Model\NodeData``)`: - -NodeData (``Neos\ContentRepository\Domain\Model\NodeData``) ------------------------------------------------------------ - -This class contains the following signals. - -nodePathChanged -^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node has changed its path. - - - - - - -.. _`Content Repository Signals Reference: NodeDataRepository (``Neos\ContentRepository\Domain\Repository\NodeDataRepository``)`: - -NodeDataRepository (``Neos\ContentRepository\Domain\Repository\NodeDataRepository``) ------------------------------------------------------------------------------------- - -This class contains the following signals. - -repositoryObjectsPersisted -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that persistEntities() in this repository finished correctly. - - - - - - -.. _`Content Repository Signals Reference: PaginateController (``Neos\ContentRepository\ViewHelpers\Widget\Controller\PaginateController``)`: - -PaginateController (``Neos\ContentRepository\ViewHelpers\Widget\Controller\PaginateController``) ------------------------------------------------------------------------------------------------- - -This class contains the following signals. - -viewResolved -^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Emit that the view is resolved. The passed ViewInterface reference, -gives the possibility to add variables to the view, -before passing it on to further rendering - - - - - - -.. _`Content Repository Signals Reference: PublishingService (``Neos\ContentRepository\Domain\Service\PublishingService``)`: - -PublishingService (``Neos\ContentRepository\Domain\Service\PublishingService``) -------------------------------------------------------------------------------- - -This class contains the following signals. - -nodePublished -^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node has been published. - -The signal emits the source node and target workspace, i.e. the node contains its source -workspace. - -nodeDiscarded -^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node has been discarded. - -The signal emits the node that has been discarded. - - - - - - -.. _`Content Repository Signals Reference: Workspace (``Neos\ContentRepository\Domain\Model\Workspace``)`: - -Workspace (``Neos\ContentRepository\Domain\Model\Workspace``) -------------------------------------------------------------- - -This class contains the following signals. - -baseWorkspaceChanged -^^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Emits a signal after the base workspace has been changed - -beforeNodePublishing -^^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Emits a signal just before a node is being published - -The signal emits the source node and target workspace, i.e. the node contains its source -workspace. - -afterNodePublishing -^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Emits a signal when a node has been published. - -The signal emits the source node and target workspace, i.e. the node contains its source -workspace. - - - - +This reference was automatically generated from code on 2024-01-12 diff --git a/Neos.Neos/Documentation/References/Signals/Flow.rst b/Neos.Neos/Documentation/References/Signals/Flow.rst index 43a8131de26..bb984a53073 100644 --- a/Neos.Neos/Documentation/References/Signals/Flow.rst +++ b/Neos.Neos/Documentation/References/Signals/Flow.rst @@ -3,7 +3,7 @@ Flow Signals Reference ====================== -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`Flow Signals Reference: AbstractAdvice (``Neos\Flow\Aop\Advice\AbstractAdvice``)`: @@ -35,17 +35,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - -Emit that the view is resolved. The passed ViewInterface reference, -gives the possibility to add variables to the view, -before passing it on to further rendering - -viewResolved -^^^^^^^^^^^^ - -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -65,17 +54,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - -Emit that the view is resolved. The passed ViewInterface reference, -gives the possibility to add variables to the view, -before passing it on to further rendering - -viewResolved -^^^^^^^^^^^^ - -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -95,8 +73,6 @@ This class contains the following signals. requestDispatched ^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Emits a signal when a Request has been dispatched The action request is not proxyable, so the signal is dispatched manually here. @@ -193,23 +169,17 @@ This class contains the following signals. authenticatedToken ^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that the specified token has been successfully authenticated. loggedOut ^^^^^^^^^ -Autogenerated Proxy Method - Signals that all active authentication tokens have been invalidated. Note: the session will be destroyed after this signal has been emitted. successfullyAuthenticated ^^^^^^^^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that authentication commenced and at least one token was authenticated. @@ -273,8 +243,6 @@ This class contains the following signals. warmupCaches ^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that caches should be warmed up. Other application parts may subscribe to this signal and execute additional @@ -346,15 +314,11 @@ This class contains the following signals. beforeControllerInvocation ^^^^^^^^^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - This signal is emitted directly before the request is been dispatched to a controller. afterControllerInvocation ^^^^^^^^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - This signal is emitted directly after the request has been dispatched to a controller and the controller returned control back to the dispatcher. @@ -431,8 +395,6 @@ This class contains the following signals. allObjectsPersisted ^^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that all persistAll() has been executed successfully. @@ -450,8 +412,6 @@ This class contains the following signals. configurationLoaded ^^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Emits a signal when the policy configuration has been loaded This signal can be used to add roles and/or privilegeTargets during runtime. In the slot make sure to receive the @@ -460,8 +420,6 @@ $policyConfiguration array by reference so you can alter it. rolesInitialized ^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Emits a signal when roles have been initialized This signal can be used to register roles during runtime. In the slot make sure to receive the $roles array by @@ -482,8 +440,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -523,8 +479,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering diff --git a/Neos.Neos/Documentation/References/Signals/Media.rst b/Neos.Neos/Documentation/References/Signals/Media.rst index 3c6e9069be8..d4c39be439f 100644 --- a/Neos.Neos/Documentation/References/Signals/Media.rst +++ b/Neos.Neos/Documentation/References/Signals/Media.rst @@ -3,7 +3,7 @@ Media Signals Reference ======================= -This reference was automatically generated from code on 2023-06-01 +This reference was automatically generated from code on 2024-01-12 .. _`Media Signals Reference: AssetCollectionController (``Neos\Media\Browser\Controller\AssetCollectionController``)`: @@ -16,8 +16,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -37,8 +35,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -58,8 +54,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -79,29 +73,21 @@ This class contains the following signals. assetCreated ^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that an asset was added. assetRemoved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that an asset was removed. assetUpdated ^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that an asset was updated. assetResourceReplaced ^^^^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that a resource on an asset has been replaced Note: when an asset resource is replaced, the assetUpdated signal is sent anyway @@ -122,8 +108,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -143,8 +127,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -164,8 +146,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -185,8 +165,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -206,8 +184,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -227,22 +203,16 @@ This class contains the following signals. thumbnailRefreshed ^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that a thumbnail was refreshed. thumbnailPersisted ^^^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that a thumbnail was persisted. thumbnailCreated ^^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that a thumbnail was created. @@ -260,8 +230,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering diff --git a/Neos.Neos/Documentation/References/Signals/Neos.rst b/Neos.Neos/Documentation/References/Signals/Neos.rst index 172d5bd3552..7714f38f12e 100644 --- a/Neos.Neos/Documentation/References/Signals/Neos.rst +++ b/Neos.Neos/Documentation/References/Signals/Neos.rst @@ -3,26 +3,7 @@ Neos Signals Reference ====================== -This reference was automatically generated from code on 2023-06-01 - - -.. _`Neos Signals Reference: AbstractCreate (``Neos\Neos\Ui\Domain\Model\Changes\AbstractCreate``)`: - -AbstractCreate (``Neos\Neos\Ui\Domain\Model\Changes\AbstractCreate``) ---------------------------------------------------------------------- - -This class contains the following signals. - -nodeCreationHandlersApplied -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals, that all changes by node creation handlers are applied - - - - +This reference was automatically generated from code on 2024-01-12 .. _`Neos Signals Reference: AbstractModuleController (``Neos\Neos\Controller\Module\AbstractModuleController``)`: @@ -35,17 +16,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - -Emit that the view is resolved. The passed ViewInterface reference, -gives the possibility to add variables to the view, -before passing it on to further rendering - -viewResolved -^^^^^^^^^^^^ - -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -65,8 +35,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -86,8 +54,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -107,8 +73,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -128,8 +92,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -149,8 +111,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -170,8 +130,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -191,8 +149,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -212,8 +168,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -223,28 +177,6 @@ before passing it on to further rendering -.. _`Neos Signals Reference: ContentContext (``Neos\Neos\Domain\Service\ContentContext``)`: - -ContentContext (``Neos\Neos\Domain\Service\ContentContext``) ------------------------------------------------------------- - -This class contains the following signals. - -beforeAdoptNode -^^^^^^^^^^^^^^^ - - - -afterAdoptNode -^^^^^^^^^^^^^^ - - - - - - - - .. _`Neos Signals Reference: ContentController (``Neos\Neos\Controller\Backend\ContentController``)`: ContentController (``Neos\Neos\Controller\Backend\ContentController``) @@ -255,15 +187,11 @@ This class contains the following signals. assetUploaded ^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that a new asset has been uploaded through the Neos Backend viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -283,8 +211,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -294,63 +220,6 @@ before passing it on to further rendering -.. _`Neos Signals Reference: Create (``Neos\Neos\Ui\Domain\Model\Changes\Create``)`: - -Create (``Neos\Neos\Ui\Domain\Model\Changes\Create``) ------------------------------------------------------ - -This class contains the following signals. - -nodeCreationHandlersApplied -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals, that all changes by node creation handlers are applied - - - - - - -.. _`Neos Signals Reference: CreateAfter (``Neos\Neos\Ui\Domain\Model\Changes\CreateAfter``)`: - -CreateAfter (``Neos\Neos\Ui\Domain\Model\Changes\CreateAfter``) ---------------------------------------------------------------- - -This class contains the following signals. - -nodeCreationHandlersApplied -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals, that all changes by node creation handlers are applied - - - - - - -.. _`Neos Signals Reference: CreateBefore (``Neos\Neos\Ui\Domain\Model\Changes\CreateBefore``)`: - -CreateBefore (``Neos\Neos\Ui\Domain\Model\Changes\CreateBefore``) ------------------------------------------------------------------ - -This class contains the following signals. - -nodeCreationHandlersApplied -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals, that all changes by node creation handlers are applied - - - - - - .. _`Neos Signals Reference: DataSourceController (``Neos\Neos\Service\Controller\DataSourceController``)`: DataSourceController (``Neos\Neos\Service\Controller\DataSourceController``) @@ -361,8 +230,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -382,8 +249,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -403,8 +268,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -424,8 +287,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -445,8 +306,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -466,8 +325,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -487,29 +344,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - -Emit that the view is resolved. The passed ViewInterface reference, -gives the possibility to add variables to the view, -before passing it on to further rendering - - - - - - -.. _`Neos Signals Reference: NodeController (``Neos\Neos\Service\Controller\NodeController``)`: - -NodeController (``Neos\Neos\Service\Controller\NodeController``) ----------------------------------------------------------------- - -This class contains the following signals. - -viewResolved -^^^^^^^^^^^^ - -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -529,8 +363,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -550,8 +382,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -561,37 +391,6 @@ before passing it on to further rendering -.. _`Neos Signals Reference: PublishingService (``Neos\Neos\Service\PublishingService``)`: - -PublishingService (``Neos\Neos\Service\PublishingService``) ------------------------------------------------------------ - -This class contains the following signals. - -nodePublished -^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node has been published. - -The signal emits the source node and target workspace, i.e. the node contains its source -workspace. - -nodeDiscarded -^^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signals that a node has been discarded. - -The signal emits the node that has been discarded. - - - - - - .. _`Neos Signals Reference: SchemaController (``Neos\Neos\Controller\Backend\SchemaController``)`: SchemaController (``Neos\Neos\Controller\Backend\SchemaController``) @@ -602,8 +401,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -623,8 +420,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -644,8 +439,6 @@ This class contains the following signals. siteChanged ^^^^^^^^^^^ -Autogenerated Proxy Method - Internal signal @@ -653,25 +446,6 @@ Internal signal -.. _`Neos Signals Reference: SiteImportService (``Neos\Neos\Domain\Service\SiteImportService``)`: - -SiteImportService (``Neos\Neos\Domain\Service\SiteImportService``) ------------------------------------------------------------------- - -This class contains the following signals. - -siteImported -^^^^^^^^^^^^ - -Autogenerated Proxy Method - -Signal that is triggered when a site has been imported successfully - - - - - - .. _`Neos Signals Reference: SiteService (``Neos\Neos\Domain\Service\SiteService``)`: SiteService (``Neos\Neos\Domain\Service\SiteService``) @@ -682,8 +456,6 @@ This class contains the following signals. sitePruned ^^^^^^^^^^ -Autogenerated Proxy Method - Signal that is triggered whenever a site has been pruned @@ -701,8 +473,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -722,8 +492,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -743,8 +511,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -764,50 +530,36 @@ This class contains the following signals. userCreated ^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that a new user, including a new account has been created. userDeleted ^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that the given user has been deleted. userUpdated ^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that the given user data has been updated. rolesAdded ^^^^^^^^^^ -Autogenerated Proxy Method - Signals that new roles have been assigned to the given account rolesRemoved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that roles have been removed to the given account userActivated ^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that the given user has been activated userDeactivated ^^^^^^^^^^^^^^^ -Autogenerated Proxy Method - Signals that the given user has been activated @@ -825,8 +577,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -846,29 +596,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - -Emit that the view is resolved. The passed ViewInterface reference, -gives the possibility to add variables to the view, -before passing it on to further rendering - - - - - - -.. _`Neos Signals Reference: WorkspaceController (``Neos\Neos\Service\Controller\WorkspaceController``)`: - -WorkspaceController (``Neos\Neos\Service\Controller\WorkspaceController``) --------------------------------------------------------------------------- - -This class contains the following signals. - -viewResolved -^^^^^^^^^^^^ - -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering @@ -888,29 +615,6 @@ This class contains the following signals. viewResolved ^^^^^^^^^^^^ -Autogenerated Proxy Method - -Emit that the view is resolved. The passed ViewInterface reference, -gives the possibility to add variables to the view, -before passing it on to further rendering - - - - - - -.. _`Neos Signals Reference: WorkspacesController (``Neos\Neos\Controller\Service\WorkspacesController``)`: - -WorkspacesController (``Neos\Neos\Controller\Service\WorkspacesController``) ----------------------------------------------------------------------------- - -This class contains the following signals. - -viewResolved -^^^^^^^^^^^^ - -Autogenerated Proxy Method - Emit that the view is resolved. The passed ViewInterface reference, gives the possibility to add variables to the view, before passing it on to further rendering diff --git a/Neos.Neos/Documentation/References/Validators/Flow.rst b/Neos.Neos/Documentation/References/Validators/Flow.rst index cd4a15ef7ba..16e5fc399bf 100644 --- a/Neos.Neos/Documentation/References/Validators/Flow.rst +++ b/Neos.Neos/Documentation/References/Validators/Flow.rst @@ -3,7 +3,7 @@ Flow Validator Reference ======================== -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`Flow Validator Reference: AggregateBoundaryValidator`: diff --git a/Neos.Neos/Documentation/References/Validators/Media.rst b/Neos.Neos/Documentation/References/Validators/Media.rst index e9882ea290b..e2b785bc308 100644 --- a/Neos.Neos/Documentation/References/Validators/Media.rst +++ b/Neos.Neos/Documentation/References/Validators/Media.rst @@ -3,7 +3,7 @@ Media Validator Reference ========================= -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`Media Validator Reference: ImageOrientationValidator`: diff --git a/Neos.Neos/Documentation/References/Validators/Party.rst b/Neos.Neos/Documentation/References/Validators/Party.rst index 209f42baec4..124a5bd2c06 100644 --- a/Neos.Neos/Documentation/References/Validators/Party.rst +++ b/Neos.Neos/Documentation/References/Validators/Party.rst @@ -3,7 +3,7 @@ Party Validator Reference ========================= -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`Party Validator Reference: AimAddressValidator`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst b/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst index 5b95b147e82..77d810ca1ba 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst @@ -3,6 +3,6 @@ Content Repository ViewHelper Reference ####################################### -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 735234dcc58..457da5bd056 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`FluidAdaptor ViewHelper Reference: f:debug`: @@ -2167,9 +2167,9 @@ Examples Header Footer - + <-- in the outer template, using the widget --> - + Foo: {foo} diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 90ce1be51a9..f5f7ff8c5d9 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst b/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst index f02ed9e8ec5..b13dce2c81d 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst @@ -3,7 +3,7 @@ Fusion ViewHelper Reference ########################### -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`Fusion ViewHelper Reference: fusion:render`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index df696b062d6..212ca4c0baf 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index 1d9bf73d4cb..a6d605b1baf 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: @@ -762,13 +762,6 @@ ViewHelper to find out if Neos is rendering the backend. -Arguments -********* - -* ``node`` (Neos\ContentRepository\Core\Projection\ContentGraph\Node, *optional*): Node - - - Examples ******** @@ -807,8 +800,6 @@ ViewHelper to find out if Neos is rendering an edit mode. Arguments ********* -* ``node`` (Neos\ContentRepository\Core\Projection\ContentGraph\Node, *optional*): Optional Node to use context from - * ``mode`` (string, *optional*): Optional rendering mode name to check if this specific mode is active @@ -868,8 +859,6 @@ ViewHelper to find out if Neos is rendering a preview mode. Arguments ********* -* ``node`` (Neos\ContentRepository\Core\Projection\ContentGraph\Node, *optional*): Optional Node to use context from - * ``mode`` (string, *optional*): Optional rendering mode name to check if this specific mode is active @@ -928,13 +917,6 @@ the ViewHelper or have "node" set as template variable at least. -Arguments -********* - -* ``node`` (Neos\ContentRepository\Core\Projection\ContentGraph\Node, *optional*): Node - - - Examples ******** diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 71d34adfa32..b8f625ad394 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2023-05-01 +This reference was automatically generated from code on 2024-01-12 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: @@ -109,7 +109,7 @@ Container tag Some output or Fluid code - + Additional output is also not compilable because of the ViewHelper @@ -277,7 +277,7 @@ Exception if it contains syntax errors. You can put child nodes in CDATA tags to avoid this. Using this ViewHelper won't have a notable effect on performance, -especially once the template is parsed. However it can lead to reduced +especially once the template is parsed. However, it can lead to reduced readability. You can use layouts and partials to split a large template into smaller parts. Using self-descriptive names for the partials can make comments redundant. @@ -617,7 +617,77 @@ Arguments * ``reverse`` (boolean, *optional*): If TRUE, iterates in reverse -* ``iteration`` (string, *optional*): The name of the variable to store iteration information (index, cycle, isFirst, isLast, isEven, isOdd) +* ``iteration`` (string, *optional*): The name of the variable to store iteration information (index, cycle, total, isFirst, isLast, isEven, isOdd) + + + + +.. _`TYPO3 Fluid ViewHelper Reference: f:format.case`: + +f:format.case +------------- + +Modifies the case of an input string to upper- or lowercase or capitalization. +The default transformation will be uppercase as in `mb_convert_case`_. + +Possible modes are: + +``lower`` + Transforms the input string to its lowercase representation + +``upper`` + Transforms the input string to its uppercase representation + +``capital`` + Transforms the input string to its first letter upper-cased, i.e. capitalization + +``uncapital`` + Transforms the input string to its first letter lower-cased, i.e. uncapitalization + +``capitalWords`` + Not supported yet: Transforms the input string to each containing word being capitalized + +Note that the behavior will be the same as in the appropriate PHP function `mb_convert_case`_; +especially regarding locale and multibyte behavior. + +.. _mb_convert_case: https://www.php.net/manual/function.mb-convert-case.php + +Examples +======== + +Default +------- + +:: + + Some Text with miXed case + +Output:: + + SOME TEXT WITH MIXED CASE + +Example with given mode +----------------------- + +:: + + someString + +Output:: + + SomeString + +:Implementation: TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\CaseViewHelper + + + + +Arguments +********* + +* ``value`` (string, *optional*): The input value. If not given, the evaluated child nodes will be used. + +* ``mode`` (string, *optional*): The case to apply, must be one of this' CASE_* constants. Defaults to uppercase application. @@ -734,6 +804,150 @@ Arguments +.. _`TYPO3 Fluid ViewHelper Reference: f:format.json`: + +f:format.json +------------- + +Wrapper for PHPs :php:`json_encode` function. +See https://www.php.net/manual/function.json-encode.php. + +Examples +======== + +Encoding a view variable +------------------------ + +:: + + {someArray -> f:format.json()} + +``["array","values"]`` +Depending on the value of ``{someArray}``. + +Associative array +----------------- + +:: + + {f:format.json(value: {foo: 'bar', bar: 'baz'})} + +``{"foo":"bar","bar":"baz"}`` + +Non associative array with forced object +---------------------------------------- + +:: + + {f:format.json(value: {0: 'bar', 1: 'baz'}, forceObject: true)} + +``{"0":"bar","1":"baz"}`` + +:Implementation: TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\JsonViewHelper + + + + +Arguments +********* + +* ``value`` (mixed, *optional*): The incoming data to convert, or null if VH children should be used + +* ``forceObject`` (bool, *optional*): Outputs an JSON object rather than an array + + + + +.. _`TYPO3 Fluid ViewHelper Reference: f:format.nl2br`: + +f:format.nl2br +-------------- + +Wrapper for PHPs :php:`nl2br` function. +See https://www.php.net/manual/function.nl2br.php. + +Examples +======== + +Default +------- + +:: + + {text_with_linebreaks} + +Text with line breaks replaced by ``
`` + +Inline notation +--------------- + +:: + + {text_with_linebreaks -> f:format.nl2br()} + +Text with line breaks replaced by ``
`` + +:Implementation: TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\Nl2brViewHelper + + + + +Arguments +********* + +* ``value`` (string, *optional*): string to format + + + + +.. _`TYPO3 Fluid ViewHelper Reference: f:format.number`: + +f:format.number +--------------- + +Formats a number with custom precision, decimal point and grouped thousands. +See https://www.php.net/manual/function.number-format.php. + +Examples +======== + +Defaults +-------- + +:: + + 423423.234 + +``423,423.20`` + +With all parameters +------------------- + +:: + + + 423423.234 + + +``423.423,2`` + +:Implementation: TYPO3Fluid\\Fluid\\ViewHelpers\\Format\\NumberViewHelper + + + + +Arguments +********* + +* ``decimals`` (int, *optional*): The number of digits after the decimal point + +* ``decimalSeparator`` (string, *optional*): The decimal point character + +* ``thousandsSeparator`` (string, *optional*): The character for grouping the thousand digits + + + + .. _`TYPO3 Fluid ViewHelper Reference: f:format.printf`: f:format.printf @@ -868,6 +1082,194 @@ Arguments +.. _`TYPO3 Fluid ViewHelper Reference: f:format.stripTags`: + +f:format.stripTags +------------------ + +Removes tags from the given string (applying PHPs :php:`strip_tags()` function) +See https://www.php.net/manual/function.strip-tags.php. + +Examples +======== + +Default notation +---------------- + +:: + + Some Text with Tags and an Ümlaut. + +Some Text with Tags and an Ümlaut. :php:`strip_tags()` applied. + +.. note:: + Encoded entities are not decoded. + +Default notation with allowedTags +--------------------------------- + +:: + + +

paragraph

span
divider
+
+ +Output:: + +

paragraph

span
divider
iframe + +Inline notation +--------------- + +:: + + {text -> f:format.stripTags()} + +Text without tags :php:`strip_tags()` applied. + +Inline notation with allowedTags +-------------------------------- + +:: + + {text -> f:format.stripTags(allowedTags: "