- new vitec:create-missing-products: MGW Diamond TOUGH, MGW Pico TOUGH, MGW Pico+ TOUGH and X-View Integrated Displays as visible records (teaser drafts, pid and sys_category assignments copied from the sibling product; titles equal the Dropbox folder names so the image importer matches immediately). IQS4 stays out - its delivery folder is empty, question to Joe - asset re-run via vitec:import-product-images after re-staging the Dropbox delivery (the misfiled Diamond-OG banner corrected into the OG inbox folder again): new photos for 95-Series, Avedia chassis, SFF PRISM and the new TOUGH/X-View records - new migrations/build_product_audit.php: generates the internal product-asset audit at /product-audit/ (same basic auth, same-origin thumbnails) - one row per record with thumbnail, detail link, badges for hero/photos/texts/resources/datasheets/solutions, flags, stats and client-side filters; re-run any time - new vitec:import-product-category-pages: builds the nine /products/<category> pages (frontend layout 9, hero/main/preFooter) from the agency "Product Category Pages" workbooks - intro paragraphs, Why-VITEC / product / solution cards, the comparison rows as ONE text element with a plain table (no comparison CType exists; FE styles it), prefooter banner to /contact, full page properties, page sorting per sitemap 3.1-3.9; the empty shells #455 and #457 are reused. Product card links resolve record-first (exact title, stripped parenthesis, family alias, unique slug prefix); ProductFamilyAliases gains ten category-level family labels. Solution cards tolerate US/UK spelling (center/centre, visualization/ visualisation). Applied and verified live (nine routes, links, tables, meta) - known gaps reported, queued for Joe: IQS4, Media Players (93/94), Chassis Systems, Express Backplanes, Accessories & Cables, PCIe Encoders, VMS Plugins & Integrations; the Vision family card awaits a flagship decision Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
126 lines
5.8 KiB
PHP
126 lines
5.8 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.
|
|
*
|
|
* IQS4 stays out on purpose: its delivery folder is empty - question to Joe.
|
|
*
|
|
* 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.'],
|
|
];
|
|
|
|
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;
|
|
}
|
|
}
|