diff --git a/src/Migration/DuplicateEventAliasMigration.php b/src/Migration/DuplicateEventAliasMigration.php new file mode 100644 index 0000000..acfa474 --- /dev/null +++ b/src/Migration/DuplicateEventAliasMigration.php @@ -0,0 +1,76 @@ +connection->createSchemaManager()->tablesExist(['tl_calendar_events'])) { + return false; + } + + return [] !== $this->findDuplicateAliases(); + } + + public function run(): MigrationResult + { + $renamed = []; + + foreach ($this->findDuplicateAliases() as $alias) { + $ids = $this->connection->fetchFirstColumn('SELECT id FROM tl_calendar_events WHERE alias = ? ORDER BY id', [$alias]); + + // ältestes Event behält den Alias + array_shift($ids); + $counter = 2; + + foreach ($ids as $id) { + do { + $candidate = $alias.'-'.$counter++; + } while (false !== $this->connection->fetchOne('SELECT id FROM tl_calendar_events WHERE alias = ?', [$candidate])); + + $this->connection->update('tl_calendar_events', ['alias' => $candidate, 'tstamp' => time()], ['id' => (int) $id]); + $renamed[] = sprintf('ID %d: %s', $id, $candidate); + } + } + + return $this->createResult(true, 'Umbenannt: '.implode(', ', $renamed)); + } + + /** + * @return list + */ + private function findDuplicateAliases(): array + { + return $this->connection->fetchFirstColumn( + "SELECT alias FROM tl_calendar_events WHERE alias != '' GROUP BY alias HAVING COUNT(*) > 1", + ); + } +} diff --git a/src/Service/EventRepository.php b/src/Service/EventRepository.php index 6e28952..265fcc0 100644 --- a/src/Service/EventRepository.php +++ b/src/Service/EventRepository.php @@ -526,7 +526,7 @@ class EventRepository */ public function update(int $eventId, array $data): void { - $eventData = $this->buildEventData($data); + $eventData = $this->buildEventData($data, $eventId); $this->connection->update( 'tl_calendar_events', @@ -631,13 +631,13 @@ class EventRepository * * @return array */ - private function buildEventData(array $data): array + private function buildEventData(array $data, ?int $eventId = null): array { $startDate = !empty($data['startDate']) ? strtotime((string) $data['startDate']) : false; $endDate = !empty($data['endDate']) ? strtotime((string) $data['endDate']) : false; $url = trim((string) ($data['url'] ?? '')); $addTime = !empty($data['addTime']); - $alias = $this->buildAlias($startDate, (string) ($data['title'] ?? '')); + $alias = $this->resolveAlias($startDate, (string) ($data['title'] ?? ''), $eventId); $startDateTimestamp = false !== $startDate ? (int) $startDate : 0; $endDateTimestamp = false !== $endDate ? (int) $endDate : 0; @@ -678,6 +678,53 @@ class EventRepository ]; } + /** + * Alias für ein Event aus dem Frontend-Formular. + * + * - Bestehende Events behalten ihren Alias (stabile URLs, auch wenn Titel oder + * Datum korrigiert werden); nur ein leerer Alias wird neu erzeugt, z. B. bei + * einer frisch duplizierten Kopie. + * - Der Alias ist immer eindeutig: belegte Aliasse bekommen -2, -3, ... angehängt. + */ + private function resolveAlias(false|int $startDate, string $title, ?int $eventId): string + { + if (null !== $eventId && $eventId > 0) { + $existing = (string) $this->connection->fetchOne('SELECT alias FROM tl_calendar_events WHERE id = ?', [$eventId]); + + if ('' !== $existing && !$this->aliasExists($existing, $eventId)) { + return $existing; + } + } + + $base = $this->buildAlias($startDate, $title); + + if ('' === $base) { + return ''; + } + + return $this->makeUniqueAlias($base, $eventId); + } + + private function makeUniqueAlias(string $base, ?int $eventId = null): string + { + $candidate = $base; + $counter = 2; + + while ($this->aliasExists($candidate, $eventId)) { + $candidate = $base.'-'.$counter++; + } + + return $candidate; + } + + private function aliasExists(string $alias, ?int $exceptEventId = null): bool + { + return false !== $this->connection->fetchOne( + 'SELECT id FROM tl_calendar_events WHERE alias = ? AND id != ? LIMIT 1', + [$alias, (int) $exceptEventId], + ); + } + private function buildAlias(false|int $startDate, string $title): string { if (false === $startDate || $startDate <= 0) {