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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions Classes/Hook/CreateMaps2RecordHook.php
Original file line number Diff line number Diff line change
Expand Up @@ -460,20 +460,17 @@ public function synchronizeColumnsFromForeignRecordWithPoiCollection(
),
);

$tableNeedsUpdate = false;
foreach ($columnRegistration->getSynchronizeColumns() as $synchronizeColumns) {
if (!$this->isValidSynchronizeConfiguration($synchronizeColumns, $foreignTableName)) {
return false;
}

$queryBuilder = $queryBuilder->set(
$synchronizeColumns['poiCollectionColumnName'],
$foreignLocationRecord[$synchronizeColumns['foreignColumnName']],
$synchronizeColumns->getPoiCollectionColumnName(),
$synchronizeColumns->getForeignColumn()->resolveValue($foreignLocationRecord),
);
$tableNeedsUpdate = true;
}

// Only execute query, if there are columns to update
$queryBuilder->executeStatement();

return true;
Expand All @@ -484,22 +481,23 @@ public function synchronizeColumnsFromForeignRecordWithPoiCollection(
*/
private function isValidSynchronizeConfiguration(SynchronizeColumn $synchronizeColumns, string $foreignTableName): bool
{
// Check, if configured foreign columnName is valid in TCA
$foreignColumnName = $synchronizeColumns->getForeignColumnName();
if (
!array_key_exists($foreignTableName, $GLOBALS['TCA'])
|| !array_key_exists($foreignColumnName, $GLOBALS['TCA'][$foreignTableName]['columns'])
|| !is_array($GLOBALS['TCA'][$foreignTableName]['columns'][$foreignColumnName]['config'])
) {
$this->messageHelper->addFlashMessage(
'Error while trying to synchronize columns of your record with maps2 record. It seems that "'
. $foreignTableName . '" is not registered as table or "'
. $foreignColumnName . '" is not a valid column in ' . $foreignTableName,
'Missing table/column in TCA',
ContextualFeedbackSeverity::ERROR,
);
// Check if every configured foreign columnName is valid in TCA
foreach ($synchronizeColumns->getForeignColumn()->getColumnNames() as $foreignColumnName) {
if (
!array_key_exists($foreignTableName, $GLOBALS['TCA'])
|| !array_key_exists($foreignColumnName, $GLOBALS['TCA'][$foreignTableName]['columns'])
|| !is_array($GLOBALS['TCA'][$foreignTableName]['columns'][$foreignColumnName]['config'])
) {
$this->messageHelper->addFlashMessage(
'Error while trying to synchronize columns of your record with maps2 record. It seems that "'
. $foreignTableName . '" is not registered as table or "'
. $foreignColumnName . '" is not a valid column in ' . $foreignTableName,
'Missing table/column in TCA',
ContextualFeedbackSeverity::ERROR,
);

return false;
return false;
}
}

return true;
Expand Down
154 changes: 154 additions & 0 deletions Classes/Tca/ForeignColumn.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php

declare(strict_types=1);

/*
* This file is part of the package jweiland/maps2.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/

namespace JWeiland\Maps2\Tca;

/**
* Resolves the value that has to be synchronized from a foreign record into a POI collection column.
*
* Can either be a single column (leaf), or a composite of further ForeignColumn instances which are
* combined with a COALESCE (first non-empty value wins) or CONCAT (join all non-empty values) strategy.
*/
final readonly class ForeignColumn
{
/**
* @param ForeignColumn[] $children
*/
private function __construct(
private ForeignColumnResolveTypeEnum $resolveType,
private string $columnName = '',
private array $children = [],
private string $glue = ' ',
) {}

/**
* Builds a ForeignColumn from the raw `foreignColumnName` TCA configuration. Accepts:
*
* - a plain string with the column name of the foreign table
* - an array with `type` (a ForeignColumnResolveTypeEnum case or its string value: coalesce|concat)
* and `columns`, where every entry of `columns` may again be a column name or a nested
* configuration array
* - a plain list of column names / nested configuration arrays, which is treated as an
* implicit `coalesce`: the first entry that resolves to a non-empty value wins
*/
public static function createFromConfiguration(mixed $configuration): ?self
{
if (is_string($configuration)) {
$columnName = trim($configuration);
if ($columnName === '') {
return null;
}

return new self(ForeignColumnResolveTypeEnum::SINGLE, columnName: $columnName);
}

if (!is_array($configuration)) {
return null;
}

if (isset($configuration['type'], $configuration['columns']) && is_array($configuration['columns'])) {
$resolveType = $configuration['type'] instanceof ForeignColumnResolveTypeEnum
? $configuration['type']
: ForeignColumnResolveTypeEnum::tryFrom((string)$configuration['type']);

if (!$resolveType instanceof ForeignColumnResolveTypeEnum || $resolveType === ForeignColumnResolveTypeEnum::SINGLE) {
return null;
}

$children = self::createChildren($configuration['columns']);
if ($children === []) {
return null;
}

return new self(
$resolveType,
children: $children,
glue: (string)($configuration['glue'] ?? ' '),
);
}

if (array_is_list($configuration)) {
$children = self::createChildren($configuration);
if ($children === []) {
return null;
}

return new self(ForeignColumnResolveTypeEnum::COALESCE, children: $children);
}

return null;
}

/**
* @return ForeignColumn[]
*/
private static function createChildren(array $columns): array
{
return array_values(array_filter(array_map(
static fn(mixed $column): ?self => self::createFromConfiguration($column),
$columns,
)));
}

public function resolveValue(array $record): string
{
return match ($this->resolveType) {
ForeignColumnResolveTypeEnum::SINGLE => trim((string)($record[$this->columnName] ?? '')),
ForeignColumnResolveTypeEnum::COALESCE => $this->resolveCoalesce($record),
ForeignColumnResolveTypeEnum::CONCAT => $this->resolveConcat($record),
};
}

/**
* Flat list of every column name involved in this (possibly nested) configuration.
* Used to validate against TCA of the foreign table.
*
* @return string[]
*/
public function getColumnNames(): array
{
if ($this->resolveType === ForeignColumnResolveTypeEnum::SINGLE) {
return [$this->columnName];
}

$columnNames = [];
foreach ($this->children as $child) {
$columnNames[] = $child->getColumnNames();
}

return array_values(array_unique(array_merge([], ...$columnNames)));
}

private function resolveCoalesce(array $record): string
{
foreach ($this->children as $child) {
$value = $child->resolveValue($record);
if ($value !== '') {
return $value;
}
}

return '';
}

private function resolveConcat(array $record): string
{
$values = array_filter(
array_map(
static fn(self $child): string => $child->resolveValue($record),
$this->children,
),
static fn(string $value): bool => $value !== '',
);

return implode($this->glue, $values);
}
}
19 changes: 19 additions & 0 deletions Classes/Tca/ForeignColumnResolveTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

/*
* This file is part of the package jweiland/maps2.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/

namespace JWeiland\Maps2\Tca;

enum ForeignColumnResolveTypeEnum: string
{
case SINGLE = 'single';
case COALESCE = 'coalesce';
case CONCAT = 'concat';
}
19 changes: 12 additions & 7 deletions Classes/Tca/Maps2Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,20 @@ private function getSynchronizeColumn(array $synchronizeColumnConfiguration): ar
private function createSynchronizeColumn(array $synchronizeColumnConfiguration): ?SynchronizeColumn
{
if (
isset($synchronizeColumnConfiguration['foreignColumnName'])
&& isset($synchronizeColumnConfiguration['poiCollectionColumnName'])
!isset($synchronizeColumnConfiguration['foreignColumnName'])
|| !isset($synchronizeColumnConfiguration['poiCollectionColumnName'])
) {
return new SynchronizeColumn(
foreignColumnName: (string)$synchronizeColumnConfiguration['foreignColumnName'] ?? '',
poiCollectionColumnName: (string)$synchronizeColumnConfiguration['poiCollectionColumnName'] ?? '',
);
return null;
}

return null;
$foreignColumn = ForeignColumn::createFromConfiguration($synchronizeColumnConfiguration['foreignColumnName']);
if (!$foreignColumn instanceof ForeignColumn) {
return null;
}

return new SynchronizeColumn(
foreignColumn: $foreignColumn,
poiCollectionColumnName: (string)$synchronizeColumnConfiguration['poiCollectionColumnName'],
);
}
}
6 changes: 3 additions & 3 deletions Classes/Tca/SynchronizeColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
final readonly class SynchronizeColumn
{
public function __construct(
private string $foreignColumnName,
private ForeignColumn $foreignColumn,
private string $poiCollectionColumnName,
) {}

public function getForeignColumnName(): string
public function getForeignColumn(): ForeignColumn
{
return $this->foreignColumnName;
return $this->foreignColumn;
}

public function getPoiCollectionColumnName(): string
Expand Down
9 changes: 9 additions & 0 deletions Documentation/ChangeLog/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
ChangeLog
=========

Version 13.1.0
==============

* FEATURE: Support coalesce/concat resolution strategies for the
`foreignColumnName` of `synchronizeColumns`, allowing fallback columns and
combined columns (including arbitrary nesting of both strategies)
* BUGFIX: Fix TypeError in CreateMaps2RecordHook when synchronizing columns
(SynchronizeColumn was accessed via array offset instead of its getters)

Version 13.0.2
==============

Expand Down
Loading
Loading