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
50 changes: 33 additions & 17 deletions Classes/Hook/CreateMaps2RecordHook.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use JWeiland\Maps2\Helper\StoragePidHelper;
use JWeiland\Maps2\Service\GeoCodeService;
use JWeiland\Maps2\Service\MapService;
use JWeiland\Maps2\Tca\ForeignColumn;
use JWeiland\Maps2\Tca\Maps2Registry;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Backend\Utility\BackendUtility;
Expand Down Expand Up @@ -483,13 +484,14 @@ public function synchronizeColumnsFromForeignRecordWithPoiCollection(

$tableNeedsUpdate = false;
foreach ($columnOptions['synchronizeColumns'] as $synchronizeColumns) {
if (!$this->isValidSynchronizeConfiguration($synchronizeColumns, $foreignTableName)) {
$foreignColumn = $this->getForeignColumnFromSynchronizeConfiguration($synchronizeColumns, $foreignTableName);
if (!$foreignColumn instanceof ForeignColumn) {
return false;
}

$queryBuilder = $queryBuilder->set(
$synchronizeColumns['poiCollectionColumnName'],
$foreignLocationRecord[$synchronizeColumns['foreignColumnName']],
$foreignColumn->resolveValue($foreignLocationRecord),
);
$tableNeedsUpdate = true;
}
Expand All @@ -506,15 +508,17 @@ public function synchronizeColumnsFromForeignRecordWithPoiCollection(
}

/**
* This method checks the synchronization options itself and if columns are configured in TCA
* Builds the ForeignColumn value object for a single `synchronizeColumns` entry and validates
* it against the TCA of the foreign table. `foreignColumnName` may either be a plain column name
* or a coalesce/concat configuration array resolving several columns of the foreign table, see
* ForeignColumn::createFromConfiguration().
*/
protected function isValidSynchronizeConfiguration(array $synchronizeColumns, string $foreignTableName): bool
protected function getForeignColumnFromSynchronizeConfiguration(array $synchronizeColumns, string $foreignTableName): ?ForeignColumn
{
// Check options itself
if (
!array_key_exists('foreignColumnName', $synchronizeColumns)
|| !array_key_exists('poiCollectionColumnName', $synchronizeColumns)
|| !is_string($synchronizeColumns['foreignColumnName'])
|| !is_string($synchronizeColumns['poiCollectionColumnName'])
) {
$this->messageHelper->addFlashMessage(
Expand All @@ -523,26 +527,38 @@ protected function isValidSynchronizeConfiguration(array $synchronizeColumns, st
ContextualFeedbackSeverity::ERROR,
);

return false;
return null;
}

// Check, if configured foreign columnName is valid in TCA
$foreignColumnName = $synchronizeColumns['foreignColumnName'];
if (
!array_key_exists($foreignTableName, $GLOBALS['TCA'])
|| !array_key_exists($foreignColumnName, $GLOBALS['TCA'][$foreignTableName]['columns'])
|| !is_array($GLOBALS['TCA'][$foreignTableName]['columns'][$foreignColumnName]['config'])
) {
$foreignColumn = ForeignColumn::createFromConfiguration($synchronizeColumns['foreignColumnName']);
if (!$foreignColumn instanceof ForeignColumn) {
$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',
'Please check your Maps registration. The key foreignColumnName has to be a column name or a valid coalesce/concat configuration.',
'Missing registration keys',
ContextualFeedbackSeverity::ERROR,
);

return false;
return null;
}

return true;
// Check, if every configured foreign columnName is valid in TCA
foreach ($foreignColumn->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 null;
}
}

return $foreignColumn;
}

/**
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';
}
5 changes: 5 additions & 0 deletions Documentation/ChangeLog/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
ChangeLog
=========

Version 12.2.0
===============

* FEATURE: Support coalesce/concat resolution strategies for synchronizeColumns foreignColumnName

Version 12.1.2
===============

Expand Down
Loading
Loading