Product text import from agency XLSX workbooks
VITEC Import module: Products tab is now a searchable/sortable product overview. "Edit Product" opens an XLSX upload with per-field source mapping (component + cell), old/new preview and checkbox apply via DataHandler; the mapping and manual matches are persisted and preselect the next workbook. Category workbooks are detected and rejected. - new: ProductXlsxReader (PhpSpreadsheet), ProductTextImportController, Products/ProductTexts templates, 4 module routes - related products: per-pair card text in new side table tx_vitec_product_related_text (survives MM rewrites), emitted as `cardtext` in the product JSON; missing MM relations added add-only - card copy read from Body Copy (D) with fallback to CTA/Card Copy (E) - the workbooks fill either depending on row type - product detail page <title> now uses seotitle with title fallback (provider made singleton and fed from the JSON renderer) - per-user recent-search badges; last loaded workbook stored per product (tx_vitec_product_workbook), Edit Product reopens on it - composer: add phpoffice/phpspreadsheet ^5.9 - diagnostics: migrations/check_workbook.php
This commit is contained in:
1 parent
12eb40614b
commit
7bd1161abf
28 files changed
+2594
-39
No files matched your search
@@ -14,43 +14,63 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
*
|
||||
* Plain table without TCA (tx_vitec_import_mapping) - the records are pure
|
||||
* tool configuration, never edited through FormEngine.
|
||||
*
|
||||
* The same row also stores record aliases (CSV identity value -> record
|
||||
* uid) for rows whose name differs slightly from the TYPO3 record, so a
|
||||
* hand-made link survives every future delivery of the same CSV.
|
||||
*/
|
||||
final class MappingRepository
|
||||
{
|
||||
private const TABLE = 'tx_vitec_import_mapping';
|
||||
|
||||
/**
|
||||
* @return array{mapping: array<string,string>, identity: string}|null
|
||||
* @return array{mapping: array<string,string>, identity: string, aliases: array<string,int>}|null
|
||||
*/
|
||||
public function load(string $model): ?array
|
||||
{
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE);
|
||||
$row = $qb->select('mapping', 'identity_field')->from(self::TABLE)
|
||||
$row = $qb->select('mapping', 'identity_field', 'record_aliases')->from(self::TABLE)
|
||||
->where($qb->expr()->eq('model', $qb->createNamedParameter($model, ParameterType::STRING)))
|
||||
->setMaxResults(1)
|
||||
->executeQuery()->fetchAssociative();
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
// A row may exist with aliases only (saveAliases before the first
|
||||
// saveMapping) - an unparsable mapping degrades to [], not to null.
|
||||
$mapping = json_decode((string)$row['mapping'], true);
|
||||
if (!is_array($mapping)) {
|
||||
return null;
|
||||
}
|
||||
$aliases = json_decode((string)($row['record_aliases'] ?? ''), true);
|
||||
return [
|
||||
'mapping' => array_map('strval', $mapping),
|
||||
'mapping' => is_array($mapping) ? array_map('strval', $mapping) : [],
|
||||
'identity' => (string)$row['identity_field'],
|
||||
'aliases' => is_array($aliases) ? array_map('intval', $aliases) : [],
|
||||
];
|
||||
}
|
||||
|
||||
/** @param array<string,string> $mapping */
|
||||
public function save(string $model, array $mapping, string $identity): void
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(self::TABLE);
|
||||
$values = [
|
||||
'mapping' => (string)json_encode($mapping, JSON_UNESCAPED_UNICODE),
|
||||
'identity_field' => $identity,
|
||||
'tstamp' => time(),
|
||||
];
|
||||
$this->upsert($model, $values);
|
||||
}
|
||||
|
||||
/** @param array<string,int> $aliases */
|
||||
public function saveAliases(string $model, array $aliases): void
|
||||
{
|
||||
$this->upsert($model, [
|
||||
'record_aliases' => (string)json_encode($aliases, JSON_UNESCAPED_UNICODE),
|
||||
'tstamp' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $values */
|
||||
private function upsert(string $model, array $values): void
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(self::TABLE);
|
||||
$exists = $connection->count('uid', self::TABLE, ['model' => $model]) > 0;
|
||||
if ($exists) {
|
||||
$connection->update(self::TABLE, $values, ['model' => $model]);
|
||||
|
||||
Reference in new issue
Block a user