From 06c9248a528d89dc4fc4fa21d29cc97fafc36c73 Mon Sep 17 00:00:00 2001 From: romanetar Date: Tue, 27 Oct 2026 20:05:20 +0100 Subject: [PATCH 1/2] fix(badge-scans): disambiguate colliding extra question columns in the badge scans CSV export An extra question labeled like a fixed column (e.g. "Notes") overwrote that column in the export. Colliding question columns are now suffixed with the question name (and the id as last resort). --- .../SponsorBadgeScanCSVSerializer.php | 37 ++++++++++++++-- tests/SponsorBadgeScanCSVSerializerTest.php | 42 +++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 tests/SponsorBadgeScanCSVSerializerTest.php diff --git a/app/ModelSerializers/Summit/Registration/SponsorBadgeScanCSVSerializer.php b/app/ModelSerializers/Summit/Registration/SponsorBadgeScanCSVSerializer.php index f6e399afe8..daed102e30 100644 --- a/app/ModelSerializers/Summit/Registration/SponsorBadgeScanCSVSerializer.php +++ b/app/ModelSerializers/Summit/Registration/SponsorBadgeScanCSVSerializer.php @@ -52,6 +52,25 @@ private static function isAllowedExtraQuestion(array $allowed_extra_questions, i return false; } + /** + * Returns a CSV column key that does not collide with the already taken ones, + * disambiguating with the question name (and the id as last resort). + * @param array $taken_keys + * @param string $label + * @param string $name + * @param int $id + * @return string + */ + private static function getUniqueColumnKey(array $taken_keys, string $label, string $name, int $id): string + { + if (!array_key_exists($label, $taken_keys)) return $label; + + $key = sprintf("%s (%s)", $label, AbstractSerializer::getCSVLabel($name)); + if (!array_key_exists($key, $taken_keys)) return $key; + + return sprintf("%s (%s-%s)", $label, AbstractSerializer::getCSVLabel($name), $id); + } + /** * @param null $expand * @param array $fields @@ -93,7 +112,13 @@ public function serialize($expand = null, array $fields = array(), array $relati if (!self::isAllowedExtraQuestion($allowed_sponsor_extra_questions, $question->getId())) continue; - $label = AbstractSerializer::getCSVLabel($question->getLabel()); + $label = self::getUniqueColumnKey + ( + $values + $sponsor_questions_values, + AbstractSerializer::getCSVLabel($question->getLabel()), + $question->getName(), + $question->getId() + ); Log::debug ( sprintf @@ -130,9 +155,15 @@ public function serialize($expand = null, array $fields = array(), array $relati foreach ($params['ticket_questions'] as $question) { if (!self::isAllowedExtraQuestion($allowed_attendee_extra_questions, $question->getId())) continue; - $label = AbstractSerializer::getCSVLabel($question->getLabel()); - if (!$question instanceof SummitOrderExtraQuestionType) continue; + + $label = self::getUniqueColumnKey + ( + $values + $sponsor_questions_values + $ticket_questions_values, + AbstractSerializer::getCSVLabel($question->getLabel()), + $question->getName(), + $question->getId() + ); $ticket_questions_values[$label] = ''; if (!is_null($ticket_owner)) { diff --git a/tests/SponsorBadgeScanCSVSerializerTest.php b/tests/SponsorBadgeScanCSVSerializerTest.php new file mode 100644 index 0000000000..7948a37f20 --- /dev/null +++ b/tests/SponsorBadgeScanCSVSerializerTest.php @@ -0,0 +1,42 @@ +setAccessible(true); + return $method->invoke(null, $taken, $label, $name, $id); + } + + public function testNoCollisionKeepsLabel(): void + { + $this->assertSame('company size', $this->uniqueKey(['notes' => 'x'], 'company size', 'COMPANY_SIZE', 1)); + } + + public function testCollisionWithFixedColumnUsesQuestionName(): void + { + $this->assertSame('notes (notes_q)', $this->uniqueKey(['notes' => 'scan notes'], 'notes', 'NOTES_Q', 10)); + } + + public function testCollisionWithSameLabelAndNameUsesQuestionId(): void + { + $taken = ['notes' => 'a', 'notes (notes_q)' => 'b']; + $this->assertSame('notes (notes_q-11)', $this->uniqueKey($taken, 'notes', 'notes_q', 11)); + } +} From a85803c08adce96a7951deeee245643d76e8773d Mon Sep 17 00:00:00 2001 From: romanetar Date: Tue, 27 Oct 2026 20:23:36 +0100 Subject: [PATCH 2/2] fix(badge-scans): make sure the id based export column key is unused too Keep appending a counter when the label, label (name) and label (name-id) keys are all already taken, so no extra question answer is overwritten. --- .../Registration/SponsorBadgeScanCSVSerializer.php | 10 ++++++++-- tests/SponsorBadgeScanCSVSerializerTest.php | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/ModelSerializers/Summit/Registration/SponsorBadgeScanCSVSerializer.php b/app/ModelSerializers/Summit/Registration/SponsorBadgeScanCSVSerializer.php index daed102e30..16117ddc81 100644 --- a/app/ModelSerializers/Summit/Registration/SponsorBadgeScanCSVSerializer.php +++ b/app/ModelSerializers/Summit/Registration/SponsorBadgeScanCSVSerializer.php @@ -54,7 +54,7 @@ private static function isAllowedExtraQuestion(array $allowed_extra_questions, i /** * Returns a CSV column key that does not collide with the already taken ones, - * disambiguating with the question name (and the id as last resort). + * disambiguating with the question name, then the id, then a counter as last resort. * @param array $taken_keys * @param string $label * @param string $name @@ -68,7 +68,13 @@ private static function getUniqueColumnKey(array $taken_keys, string $label, str $key = sprintf("%s (%s)", $label, AbstractSerializer::getCSVLabel($name)); if (!array_key_exists($key, $taken_keys)) return $key; - return sprintf("%s (%s-%s)", $label, AbstractSerializer::getCSVLabel($name), $id); + $key = sprintf("%s (%s-%s)", $label, AbstractSerializer::getCSVLabel($name), $id); + $base = $key; + $i = 2; + while (array_key_exists($key, $taken_keys)) { + $key = sprintf("%s-%s", $base, $i++); + } + return $key; } /** diff --git a/tests/SponsorBadgeScanCSVSerializerTest.php b/tests/SponsorBadgeScanCSVSerializerTest.php index 7948a37f20..e9c348b18c 100644 --- a/tests/SponsorBadgeScanCSVSerializerTest.php +++ b/tests/SponsorBadgeScanCSVSerializerTest.php @@ -39,4 +39,10 @@ public function testCollisionWithSameLabelAndNameUsesQuestionId(): void $taken = ['notes' => 'a', 'notes (notes_q)' => 'b']; $this->assertSame('notes (notes_q-11)', $this->uniqueKey($taken, 'notes', 'notes_q', 11)); } + + public function testCollisionWithIdKeyUsesCounter(): void + { + $taken = ['notes' => 'a', 'notes (notes_q)' => 'b', 'notes (notes_q-11)' => 'c']; + $this->assertSame('notes (notes_q-11)-2', $this->uniqueKey($taken, 'notes', 'notes_q', 11)); + } }