Import old-site downloads and add a type taxonomy
New CLI command vitec:import-downloads: reads the JSON export of the old site (www.vitec.com/import), cuts it out of the surrounding page template by brace counting, and imports the downloads idempotently by slug. Scope is deliberately narrow: only files under /fileadmin/downloads/Collateral/, records without title or file are skipped, records sharing one file_url are merged. Files are fetched resumably into fileadmin/downloads/Collateral/ and stored on pid 29. Legacy filenames are normalized on fetch so every imported file matches the <prefix>__<filetype>__<NN>-<letter> version convention: __NN_A -> __NN-A, ___NN -> __NN, __NNA -> __NN-A, and a bare __NN gets -A appended as its initial revision. Verified against the full export: all 179 names match afterwards, none of the already-correct ones change. tx_vitec_domain_model_download gains a second category field "type" (display taxonomy: Datasheet, Success Story, Brochure, Software) next to the filename-driven filetype categories. No SQL change needed - the core generates columns for category fields. The three download renderers emit the field as a string analogous to filetype; MM rows are distinguished by fieldname, existing queries filter on it and remain unaffected. Import values are matched via the custom columns sys_category.filetype and .type (falling back to title); missing categories are created (filetype under --category-parent, default 4; type under a "Download Type" parent). getDownloadFile() in the three download renderers gains a filepath fallback (FAL -> convention -> filepath) as a safety net for any future filename that escapes the convention - previously such records emitted file: null. Architecture spec bumped to v1.7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
27cc22dc69
commit
baa0fdd331
7 files changed
+926
-7
No files matched your search
@@ -265,10 +265,54 @@ class DatasheetsJsonRenderer
|
||||
'description' => RteResolver::html($download['description'] ?? ''),
|
||||
'tstamp' => (int)($download['tstamp'] ?? 0),
|
||||
'filetype' => $filetype,
|
||||
'type' => $this->getDownloadTypeLabel($uid),
|
||||
'file' => $this->getDownloadFile($uid, (string)($download['fileprefix'] ?? ''), $filetype),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Display taxonomy from the `type` category field (sys_category.type,
|
||||
* fallback title) - analogous to getDownloadFileType()/`categories`,
|
||||
* distinguished in the MM table by fieldname = 'type'.
|
||||
*/
|
||||
private function getDownloadTypeLabel(int $downloadUid): string
|
||||
{
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_category');
|
||||
$categories = $qb
|
||||
->select('c.type', 'c.title')
|
||||
->from('sys_category', 'c')
|
||||
->join(
|
||||
'c',
|
||||
'sys_category_record_mm',
|
||||
'mm',
|
||||
'mm.uid_local = c.uid AND mm.tablenames = ' .
|
||||
$qb->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING) .
|
||||
' AND mm.fieldname = ' .
|
||||
$qb->createNamedParameter('type', ParameterType::STRING)
|
||||
)
|
||||
->where(
|
||||
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER)),
|
||||
$qb->expr()->eq('c.deleted', 0),
|
||||
$qb->expr()->eq('c.hidden', 0)
|
||||
)
|
||||
->orderBy('mm.sorting', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
foreach ($categories as $category) {
|
||||
$label = trim((string)($category['type'] ?? ''));
|
||||
if ($label === '') {
|
||||
$label = trim((string)($category['title'] ?? ''));
|
||||
}
|
||||
if ($label !== '') {
|
||||
return $label;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getDownloadFileType(int $downloadUid): string
|
||||
{
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
@@ -414,7 +458,8 @@ class DatasheetsJsonRenderer
|
||||
->fetchAssociative();
|
||||
|
||||
if (!$row) {
|
||||
return $this->resolveFileByConvention($fileprefix, $filetype);
|
||||
return $this->resolveFileByConvention($fileprefix, $filetype)
|
||||
?? $this->resolveFileByFilepath($downloadUid);
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -506,6 +551,57 @@ class DatasheetsJsonRenderer
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Last-resort file resolution via the record's `filepath` column - for
|
||||
* filenames outside the <prefix>__<filetype>__<NN>-<letter> convention
|
||||
* (the 2026-08 import left a handful of such legacy names in place).
|
||||
* Same payload shape as resolveFileByConvention(). Exception-safe.
|
||||
*/
|
||||
private function resolveFileByFilepath(int $downloadUid): ?array
|
||||
{
|
||||
try {
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_vitec_domain_model_download');
|
||||
$row = $qb
|
||||
->select('filepath')
|
||||
->from('tx_vitec_domain_model_download')
|
||||
->where(
|
||||
$qb->expr()->eq('uid', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER))
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
|
||||
$filepath = trim((string)($row['filepath'] ?? ''));
|
||||
if ($filepath === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$relative = '/' . ltrim($filepath, '/');
|
||||
$fullPath = Environment::getPublicPath() . $relative;
|
||||
if (!is_file($fullPath) || !is_readable($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = basename($fullPath);
|
||||
$extension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
|
||||
$mimeType = function_exists('mime_content_type') ? (string)(mime_content_type($fullPath) ?: '') : '';
|
||||
|
||||
return [
|
||||
'uid' => 0,
|
||||
'name' => $name,
|
||||
'url' => rtrim(dirname($relative), '/') . '/' . rawurlencode($name),
|
||||
'thumbnail' => null,
|
||||
'size' => (int)(filesize($fullPath) ?: 0),
|
||||
'extension' => $extension,
|
||||
'mimeType' => $mimeType,
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function letterSequenceToRank(string $letters): int
|
||||
{
|
||||
$rank = 0;
|
||||
|
||||
@@ -261,6 +261,7 @@ class DownloadcardJsonRenderer
|
||||
'filepath' => (string)($download['filepath'] ?? ''),
|
||||
'fileprefix' => $fileprefix,
|
||||
'filetype' => $filetype,
|
||||
'type' => $this->getDownloadTypeLabel($uid),
|
||||
'private_download' => (bool)($download['private_download'] ?? false),
|
||||
'hideonapp' => (bool)($download['hideonapp'] ?? false),
|
||||
'hideonwebsite' => (bool)($download['hideonwebsite'] ?? false),
|
||||
@@ -270,6 +271,49 @@ class DownloadcardJsonRenderer
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Display taxonomy from the `type` category field (sys_category.type,
|
||||
* fallback title) - analogous to getDownloadFileType()/`categories`,
|
||||
* distinguished in the MM table by fieldname = 'type'.
|
||||
*/
|
||||
private function getDownloadTypeLabel(int $downloadUid): string
|
||||
{
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_category');
|
||||
$categories = $qb
|
||||
->select('c.type', 'c.title')
|
||||
->from('sys_category', 'c')
|
||||
->join(
|
||||
'c',
|
||||
'sys_category_record_mm',
|
||||
'mm',
|
||||
'mm.uid_local = c.uid AND mm.tablenames = ' .
|
||||
$qb->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING) .
|
||||
' AND mm.fieldname = ' .
|
||||
$qb->createNamedParameter('type', ParameterType::STRING)
|
||||
)
|
||||
->where(
|
||||
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER)),
|
||||
$qb->expr()->eq('c.deleted', 0),
|
||||
$qb->expr()->eq('c.hidden', 0)
|
||||
)
|
||||
->orderBy('mm.sorting', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
foreach ($categories as $category) {
|
||||
$label = trim((string)($category['type'] ?? ''));
|
||||
if ($label === '') {
|
||||
$label = trim((string)($category['title'] ?? ''));
|
||||
}
|
||||
if ($label !== '') {
|
||||
return $label;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getDownloadFileType(int $downloadUid): string
|
||||
{
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
@@ -334,7 +378,8 @@ class DownloadcardJsonRenderer
|
||||
->fetchAssociative();
|
||||
|
||||
if (!$row) {
|
||||
return $this->resolveFileByConvention($fileprefix, $filetype);
|
||||
return $this->resolveFileByConvention($fileprefix, $filetype)
|
||||
?? $this->resolveFileByFilepath($downloadUid);
|
||||
}
|
||||
|
||||
$thumbnailUrl = null;
|
||||
@@ -437,6 +482,57 @@ class DownloadcardJsonRenderer
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Last-resort file resolution via the record's `filepath` column - for
|
||||
* filenames outside the <prefix>__<filetype>__<NN>-<letter> convention
|
||||
* (the 2026-08 import left a handful of such legacy names in place).
|
||||
* Same payload shape as resolveFileByConvention(). Exception-safe.
|
||||
*/
|
||||
private function resolveFileByFilepath(int $downloadUid): ?array
|
||||
{
|
||||
try {
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_vitec_domain_model_download');
|
||||
$row = $qb
|
||||
->select('filepath')
|
||||
->from('tx_vitec_domain_model_download')
|
||||
->where(
|
||||
$qb->expr()->eq('uid', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER))
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
|
||||
$filepath = trim((string)($row['filepath'] ?? ''));
|
||||
if ($filepath === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$relative = '/' . ltrim($filepath, '/');
|
||||
$fullPath = Environment::getPublicPath() . $relative;
|
||||
if (!is_file($fullPath) || !is_readable($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = basename($fullPath);
|
||||
$extension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
|
||||
$mimeType = function_exists('mime_content_type') ? (string)(mime_content_type($fullPath) ?: '') : '';
|
||||
|
||||
return [
|
||||
'uid' => 0,
|
||||
'name' => $name,
|
||||
'url' => rtrim(dirname($relative), '/') . '/' . rawurlencode($name),
|
||||
'thumbnail' => null,
|
||||
'size' => (int)(filesize($fullPath) ?: 0),
|
||||
'extension' => $extension,
|
||||
'mimeType' => $mimeType,
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function letterSequenceToRank(string $letters): int
|
||||
{
|
||||
$rank = 0;
|
||||
|
||||
@@ -265,6 +265,7 @@ class DownloadcardcollectionJsonRenderer
|
||||
'filepath' => (string)($download['filepath'] ?? ''),
|
||||
'fileprefix' => $fileprefix,
|
||||
'filetype' => $filetype,
|
||||
'type' => $this->getDownloadTypeLabel($uid),
|
||||
'private_download' => (bool)($download['private_download'] ?? false),
|
||||
'hideonapp' => (bool)($download['hideonapp'] ?? false),
|
||||
'hideonwebsite' => (bool)($download['hideonwebsite'] ?? false),
|
||||
@@ -274,6 +275,49 @@ class DownloadcardcollectionJsonRenderer
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Display taxonomy from the `type` category field (sys_category.type,
|
||||
* fallback title) - analogous to getDownloadFileType()/`categories`,
|
||||
* distinguished in the MM table by fieldname = 'type'.
|
||||
*/
|
||||
private function getDownloadTypeLabel(int $downloadUid): string
|
||||
{
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_category');
|
||||
$categories = $qb
|
||||
->select('c.type', 'c.title')
|
||||
->from('sys_category', 'c')
|
||||
->join(
|
||||
'c',
|
||||
'sys_category_record_mm',
|
||||
'mm',
|
||||
'mm.uid_local = c.uid AND mm.tablenames = ' .
|
||||
$qb->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING) .
|
||||
' AND mm.fieldname = ' .
|
||||
$qb->createNamedParameter('type', ParameterType::STRING)
|
||||
)
|
||||
->where(
|
||||
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER)),
|
||||
$qb->expr()->eq('c.deleted', 0),
|
||||
$qb->expr()->eq('c.hidden', 0)
|
||||
)
|
||||
->orderBy('mm.sorting', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
foreach ($categories as $category) {
|
||||
$label = trim((string)($category['type'] ?? ''));
|
||||
if ($label === '') {
|
||||
$label = trim((string)($category['title'] ?? ''));
|
||||
}
|
||||
if ($label !== '') {
|
||||
return $label;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getDownloadFileType(int $downloadUid): string
|
||||
{
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
@@ -338,7 +382,8 @@ class DownloadcardcollectionJsonRenderer
|
||||
->fetchAssociative();
|
||||
|
||||
if (!$row) {
|
||||
return $this->resolveFileByConvention($fileprefix, $filetype);
|
||||
return $this->resolveFileByConvention($fileprefix, $filetype)
|
||||
?? $this->resolveFileByFilepath($downloadUid);
|
||||
}
|
||||
|
||||
|
||||
@@ -441,6 +486,57 @@ class DownloadcardcollectionJsonRenderer
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Last-resort file resolution via the record's `filepath` column - for
|
||||
* filenames outside the <prefix>__<filetype>__<NN>-<letter> convention
|
||||
* (the 2026-08 import left a handful of such legacy names in place).
|
||||
* Same payload shape as resolveFileByConvention(). Exception-safe.
|
||||
*/
|
||||
private function resolveFileByFilepath(int $downloadUid): ?array
|
||||
{
|
||||
try {
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_vitec_domain_model_download');
|
||||
$row = $qb
|
||||
->select('filepath')
|
||||
->from('tx_vitec_domain_model_download')
|
||||
->where(
|
||||
$qb->expr()->eq('uid', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER))
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
|
||||
$filepath = trim((string)($row['filepath'] ?? ''));
|
||||
if ($filepath === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$relative = '/' . ltrim($filepath, '/');
|
||||
$fullPath = Environment::getPublicPath() . $relative;
|
||||
if (!is_file($fullPath) || !is_readable($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = basename($fullPath);
|
||||
$extension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
|
||||
$mimeType = function_exists('mime_content_type') ? (string)(mime_content_type($fullPath) ?: '') : '';
|
||||
|
||||
return [
|
||||
'uid' => 0,
|
||||
'name' => $name,
|
||||
'url' => rtrim(dirname($relative), '/') . '/' . rawurlencode($name),
|
||||
'thumbnail' => null,
|
||||
'size' => (int)(filesize($fullPath) ?: 0),
|
||||
'extension' => $extension,
|
||||
'mimeType' => $mimeType,
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function letterSequenceToRank(string $letters): int
|
||||
{
|
||||
$rank = 0;
|
||||
|
||||
Reference in new issue
Block a user