diff --git a/packages/vitec/Classes/Command/SeedProductFeaturesCommand.php b/packages/vitec/Classes/Command/SeedProductFeaturesCommand.php index 2981d4a..a2bdc0f 100644 --- a/packages/vitec/Classes/Command/SeedProductFeaturesCommand.php +++ b/packages/vitec/Classes/Command/SeedProductFeaturesCommand.php @@ -131,6 +131,36 @@ final class SeedProductFeaturesCommand extends Command } } + // second pass: token containment, e.g. theme "ManagementFoundation" + // inside the card title "Aetria management foundation" + foreach ($pairs as $i => $pair) { + if (isset($pair['icon'])) { + continue; + } + foreach (array_keys(array_diff_key($iconByTheme, $usedThemes)) as $theme) { + $titleTokens = $this->tokens($pair['title']); + $themeTokens = $this->tokens($theme); + if ($titleTokens !== [] && $themeTokens !== [] + && (array_diff($themeTokens, $titleTokens) === [] || array_diff($titleTokens, $themeTokens) === []) + ) { + $pairs[$i]['icon'] = $iconByTheme[$theme]; + $usedThemes[$theme] = true; + break; + } + } + } + + // last pair standing: a complete set (#icons == #cards) with exactly + // one icon and one card left over - they belong to each other + if (count($iconByTheme) === count($pairs)) { + $freeThemes = array_keys(array_diff_key($iconByTheme, $usedThemes)); + $freePairs = array_keys(array_filter($pairs, static fn(array $p): bool => !isset($p['icon']))); + if (count($freeThemes) === 1 && count($freePairs) === 1) { + $pairs[$freePairs[0]]['icon'] = $iconByTheme[$freeThemes[0]]; + $usedThemes[$freeThemes[0]] = true; + } + } + foreach ($pairs as $pair) { $output->writeln(sprintf( ' "%s"%s%s', @@ -281,6 +311,19 @@ final class SeedProductFeaturesCommand extends Command return $bestDistance <= 3 ? $best : null; } + /** + * Lowercase word list (camelCase split, stop words removed) for the + * token-containment pass. + * + * @return string[] + */ + private function tokens(string $value): array + { + $words = preg_split('/(?=[A-Z][a-z])|[^A-Za-z0-9]+/', $value, -1, PREG_SPLIT_NO_EMPTY) ?: []; + $words = array_map(static fn(string $w): string => mb_strtolower($w), $words); + return array_values(array_filter($words, static fn(string $w): bool => !in_array($w, self::STOPWORDS, true))); + } + /** lowercase, letters/digits only; optionally with stop words removed (camelCase themes are split first) */ private function norm(string $value, bool $dropStopwords): string {