Batch of 2026-10-28/29 plus 2026-11-03: - NEW vitec:import-datapath-stories: 22 story packages from the agency dump (docx + Links/ assets) as usecase records with content, quotes, images, customer logos, download cards; --repair-quotes normalizes attribution lines; asset backfill for existing stories - /products grid incident repaired (fix_products_grid, fix_graphics_grid, check_grid_page): containers unhidden, 46 duplicate cards soft-deleted, section order restored; BuildProductPortfolio and ImportProductCategoryPages now GUARD against re-runs on pages that already carry vitec_cols_% containers - ' double-encoding fixed: render-pipeline importers write literal apostrophes (ENT_NOQUOTES), fix_double_entities literalizes existing rows - ticket quick wins (ticket_quickwins_20261028), broadcast market page cleanup, AttachCardImages market-card handling, SubmarketWorkbook META REPAIR, story content/video fixes (fix_story_*) - ProvideProductCatalog: transitional v1 fields removed (categories uid/link, legacyGroupFields, item products teaser) - pure v2 shape (ticket 0024-FE); search renderer keeps its teaser - BuildProductOverviews: heroes get the numbered placeholder background (placeholders/pages/<uid>-01.png, wide grey fallback) - backfill on existing pages with empty hero_bgimage, attached at creation for new pages; existing references always win (ticket 0026-FE); 40 placeholder PNGs shipped to fileadmin (not tracked) - AttachPageMedia: --depth=3 with sysfolders as pass-through parents for the overview pages' -08 menu tiles - NEW migrations/fec_isolation_test.sh: two-user live test proving fe_comment ticket isolation (backend verified tight, cross-user sighting traced to the FE page-JSON cache, ticket 0027-BE) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
132 lines
6.2 KiB
PHP
132 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Evomedien\Vitec\Command;
|
|
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
use TYPO3\CMS\Core\Core\Bootstrap;
|
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Creates the four product records the agency delivery references but the
|
|
* database never had (decision Olli 2026-10-27; they were a sitemap-tracker
|
|
* question before, their asset folders arrived with the Dropbox delivery):
|
|
*
|
|
* MGW Diamond TOUGH, MGW Pico TOUGH, MGW Pico+ TOUGH,
|
|
* X-View Integrated Displays
|
|
*
|
|
* Each new record copies pid and sys_category assignments from a named
|
|
* SIBLING product (so catalog panel and product finder place it correctly)
|
|
* and is created VISIBLE (dev decision Olli 2026-10-27) with a short
|
|
* placeholder teaser (Claude draft, English) - replace it once the agency
|
|
* texts arrive. Titles equal
|
|
* the Dropbox folder names, so a vitec:import-product-images re-run attaches
|
|
* the delivered banner/carousel assets immediately.
|
|
*
|
|
* 2026-10-28: IQS4 and VisionSC-SDI4 added - the fresh Dropbox dump
|
|
* delivered their banners, and both datasheets already sit in the pool.
|
|
*
|
|
* vendor/bin/typo3 vitec:create-missing-products
|
|
* vendor/bin/typo3 vitec:create-missing-products --apply
|
|
*/
|
|
#[AsCommand(
|
|
name: 'vitec:create-missing-products',
|
|
description: 'Create the missing TOUGH / X-View product records (dry-run by default)'
|
|
)]
|
|
final class CreateMissingProductsCommand extends Command
|
|
{
|
|
private const TABLE = 'tx_vitec_domain_model_product';
|
|
|
|
/** title (= Dropbox folder name), slug, sibling slug (pid + categories donor), teaser draft */
|
|
private const PRODUCTS = [
|
|
['MGW Diamond TOUGH', 'mgw-diamond-tough', 'mgw-diamond-sdi-encoder',
|
|
'Rugged four-channel HEVC/H.264 encoder built for harsh and mobile operational environments.'],
|
|
['MGW Pico TOUGH', 'mgw-pico-tough', 'mgw-pico-portable-encoder',
|
|
'Rugged portable H.264 encoder for low-latency video contribution in the field.'],
|
|
['MGW Pico+ TOUGH', 'mgw-pico-plus-tough', 'mgw-pico-portable-encoder',
|
|
'Rugged portable HEVC/H.264 encoder for demanding field deployments.'],
|
|
['X-View Integrated Displays', 'x-view-integrated-displays', 'x-player-end-points-xp23-xp25-xp26',
|
|
'Commercial displays with an integrated IPTV end-point for TV and signage delivery.'],
|
|
// 2026-10-28 (delivery brought banners; datasheets already in the pool)
|
|
['IQS4', 'iqs4', 'image-4k',
|
|
'Distribute a single 4K source across multiple displays with a compact 4K splitter.'],
|
|
['VisionSC-SDI4', 'visionsc-sdi4', 'visionsc-s2',
|
|
'Four-channel SDI capture card for professional video and visualization workflows.'],
|
|
];
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this->addOption('apply', null, InputOption::VALUE_NONE, 'Write changes (default: dry run)');
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
{
|
|
Bootstrap::initializeBackendAuthentication();
|
|
$apply = (bool)$input->getOption('apply');
|
|
$pool = GeneralUtility::makeInstance(ConnectionPool::class);
|
|
$conn = $pool->getConnectionForTable(self::TABLE);
|
|
$output->writeln(($apply ? 'APPLY' : 'DRY-RUN'));
|
|
|
|
$datamap = [];
|
|
$index = 0;
|
|
foreach (self::PRODUCTS as [$title, $slug, $siblingSlug, $teaser]) {
|
|
$existing = $conn->fetchAssociative(
|
|
'SELECT uid FROM ' . self::TABLE . ' WHERE deleted = 0 AND (slug = ? OR title = ?)', [$slug, $title]
|
|
);
|
|
if ($existing) {
|
|
$output->writeln(sprintf(' = "%s" exists (#%d) - skipped', $title, (int)$existing['uid']));
|
|
continue;
|
|
}
|
|
$sibling = $conn->fetchAssociative(
|
|
'SELECT uid, pid FROM ' . self::TABLE . ' WHERE deleted = 0 AND slug = ?', [$siblingSlug]
|
|
);
|
|
if (!$sibling) {
|
|
$output->writeln(sprintf(' <error>! sibling %s not found - "%s" skipped</error>', $siblingSlug, $title));
|
|
continue;
|
|
}
|
|
$categories = $pool->getConnectionForTable('sys_category_record_mm')->fetchFirstColumn(
|
|
"SELECT uid_local FROM sys_category_record_mm
|
|
WHERE tablenames = ? AND fieldname = 'categories' AND uid_foreign = ? ORDER BY sorting_foreign",
|
|
[self::TABLE, (int)$sibling['uid']]
|
|
);
|
|
$datamap[self::TABLE]['NEWPROD' . (++$index)] = [
|
|
'pid' => (int)$sibling['pid'],
|
|
'title' => $title,
|
|
'slug' => $slug,
|
|
'teaser' => $teaser,
|
|
'categories' => implode(',', array_map('intval', $categories)),
|
|
];
|
|
$output->writeln(sprintf(' + "%s" (slug %s, teaser draft) - pid %d + %d categorie(s) from sibling #%d',
|
|
$title, $slug, (int)$sibling['pid'], count($categories), (int)$sibling['uid']));
|
|
}
|
|
|
|
if (!$apply) {
|
|
$output->writeln("\nDRY RUN - nothing written. Add --apply, then re-run vitec:import-product-images.");
|
|
return Command::SUCCESS;
|
|
}
|
|
if ($datamap === []) {
|
|
$output->writeln('Nothing to create.');
|
|
return Command::SUCCESS;
|
|
}
|
|
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
|
$dataHandler->start($datamap, []);
|
|
$dataHandler->process_datamap();
|
|
if ($dataHandler->errorLog !== []) {
|
|
$output->writeln('<error>DataHandler: ' . implode(' | ', $dataHandler->errorLog) . '</error>');
|
|
return Command::FAILURE;
|
|
}
|
|
foreach ($dataHandler->substNEWwithIDs as $newUid) {
|
|
$output->writeln(' created #' . (int)$newUid);
|
|
}
|
|
$output->writeln('<info>Done. Now run vitec:import-product-images (dry-run, then --apply) to attach the assets; replace the teaser drafts once the agency texts arrive.</info>');
|
|
return Command::SUCCESS;
|
|
}
|
|
}
|