Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -645,11 +645,12 @@ function () use ($summit) {
];

foreach ($summit->getBadgeFeaturesTypes() as $featuresType) {
$allowed_columns[] = $featuresType->getName();
$allowed_columns[] = sprintf('%s%s', ISummitOrderService::BadgeFeatureColumnPrefix, $featuresType->getName());
}

foreach ($summit->getOrderExtraQuestionsByUsage(SummitOrderExtraQuestionTypeConstants::TicketQuestionUsage) as $question) {
$allowed_columns[] = $question->getLabel();
// must match the serializer-emitted column exactly ( prefixed, sanitized label )
$allowed_columns[] = sprintf('%s%s', ISummitOrderService::ExtraQuestionColumnPrefix, html_entity_decode(strip_tags($question->getLabel())));
}

$columns_param = Request::input("columns", "");
Expand Down Expand Up @@ -745,7 +746,7 @@ public function ingestExternalTicketData($summit_id)
path: '/api/v1/summits/{id}/tickets/csv/template',
operationId: 'getTicketImportTemplate',
summary: 'Get ticket import template',
description: 'Returns a CSV template for importing ticket data. Includes one column per summit badge feature name plus one extra_question:{question name} column per summit order extra question (Ticket/Both usage).',
description: 'Returns a CSV template for importing ticket data. Includes one badge_feature:{feature name} column per summit badge feature plus one extra_question:{question name} column per summit order extra question (Ticket/Both usage).',
security: [['summit_tickets_oauth2' => [
SummitScopes::WriteSummitData,
SummitScopes::WriteRegistrationData,
Expand Down Expand Up @@ -788,8 +789,8 @@ public function getImportTicketDataTemplate($summit_id)
* ticket_promo_code (optional)
* badge_type_id (optional)
* badge_type_name (optional)
* badge_features (optional, one col per badge feature name)
* extra_question:{question name} (optional, one col per order extra question - Ticket/Both usage)
* badge_feature:{feature name} (optional, one col per badge feature; bare feature name accepted for legacy csvs)
* extra_question:{question name} (optional, one col per order extra question - Ticket/Both usage; label accepted too)
*/

$summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->getResourceServerContext())->find($summit_id);
Expand All @@ -814,7 +815,7 @@ public function getImportTicketDataTemplate($summit_id)

// badge features for summit
foreach ($summit->getBadgeFeaturesTypes() as $featuresType) {
$row[$featuresType->getName()] = '';
$row[sprintf('%s%s', ISummitOrderService::BadgeFeatureColumnPrefix, $featuresType->getName())] = '';
}

// order extra questions for summit ( ticket / attendee scoped ones )
Expand All @@ -841,7 +842,7 @@ public function getImportTicketDataTemplate($summit_id)
path: '/api/v1/summits/{id}/tickets/csv',
operationId: 'importTicketData',
summary: 'Import ticket data from CSV',
description: 'Imports ticket data from a CSV file. Supported columns: id, number, attendee_email, attendee_first_name, attendee_last_name, attendee_tags, attendee_company, attendee_company_id, ticket_type_name, ticket_type_id, promo_code_id, promo_code, ticket_promo_code, badge_type_id, badge_type_name, one column per badge feature name (1/0) and one extra_question:{question name} column per order extra question (Ticket/Both usage; for list type questions use the value name/label, "|" separated for multi value).',
description: 'Imports ticket data from a CSV file. Supported columns: id, number, attendee_email, attendee_first_name, attendee_last_name, attendee_tags, attendee_company, attendee_company_id, ticket_type_name, ticket_type_id, promo_code_id, promo_code, ticket_promo_code, badge_type_id, badge_type_name, one badge_feature:{feature name} column per badge feature (1/0; bare feature name accepted for legacy CSVs) and one extra_question:{question name} column per order extra question (Ticket/Both usage; question label accepted too; for list type questions use the value name/label, "|" separated for multi value).',
security: [['summit_tickets_oauth2' => [
SummitScopes::WriteSummitData,
SummitScopes::WriteRegistrationData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Models\Foundation\ExtraQuestions\ExtraQuestionType;
use App\Services\Model\ISummitOrderService;
use models\summit\SummitAttendeeTicket;
use models\summit\SummitBadgeFeatureType;
use models\summit\SummitOrderExtraQuestionType;
Expand Down Expand Up @@ -73,7 +75,9 @@ public function serialize($expand = null, array $fields = [], array $relations =
$ticket_features = $ticket->getBadgeFeaturesNames();
foreach ($params['features_types'] as $features_type) {
if (!$features_type instanceof SummitBadgeFeatureType) continue;
$values[$features_type->getName()] = in_array($features_type->getName(), $ticket_features) ? '1' : '0';
// namespaced column, matches the ticket data import convention ( round-trip )
$feature_column = sprintf('%s%s', ISummitOrderService::BadgeFeatureColumnPrefix, $features_type->getName());
$values[$feature_column] = in_array($features_type->getName(), $ticket_features) ? '1' : '0';
}
}

Expand All @@ -86,17 +90,19 @@ public function serialize($expand = null, array $fields = [], array $relations =

$question_label = self::$questionLabelCache[$question->getId()]
??= html_entity_decode(strip_tags($question->getLabel()));
$values[$question_label] = '';
// namespaced column, matches the ticket data import convention ( round-trip )
$question_column = sprintf('%s%s', ISummitOrderService::ExtraQuestionColumnPrefix, $question_label);
$values[$question_column] = '';

if (!is_null($ticket_owner)) {
$value = $answersByOwner[$ticket_owner->getId()][$question->getId()] ?? null;
if(is_null($value)) continue;

$cacheKey = $question->getId() . '|' . $value;
$value = self::$niceValueCache[$cacheKey]
??= $question->getNiceValue($value);
??= self::formatAnswerValue($question, $value);

$values[$question_label] = $value;
$values[$question_column] = $value;
}
}
}
Expand Down Expand Up @@ -141,4 +147,26 @@ public function serialize($expand = null, array $fields = [], array $relations =

return $values;
}

/**
* Multi-value answers are joined with the ticket data import's value separator instead of
* getNiceValue's comma, so exported cells re-import as-is ( labels may legitimately contain
* commas, which would corrupt a comma-joined cell on re-import ).
* @param SummitOrderExtraQuestionType $question
* @param string $value
* @return string
*/
private static function formatAnswerValue(SummitOrderExtraQuestionType $question, string $value): string
{
if (!$question->allowsValues())
return strval($question->getNiceValue($value));

$labels = [];
foreach (explode(ExtraQuestionType::QuestionChoicesCharSeparator, $value) as $value_id) {
$label = $question->getNiceValue(trim($value_id));
if (empty($label)) continue;
$labels[] = $label;
}
return implode(ISummitOrderService::ExtraQuestionValueSeparator, $labels);
}
Comment on lines +159 to +171

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

empty($label) silently drops a legitimate label equal to "0".

PHP's empty() treats the string "0" as empty (empty("0") === true), so a checkbox/list value whose nice label is literally "0" would be silently excluded from the exported cell, causing data loss during export.

🐛 Proposed fix using a strict check
         $labels = [];
         foreach (explode(ExtraQuestionType::QuestionChoicesCharSeparator, $value) as $value_id) {
             $label = $question->getNiceValue(trim($value_id));
-            if (empty($label)) continue;
+            if ($label === null || $label === '') continue;
             $labels[] = $label;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static function formatAnswerValue(SummitOrderExtraQuestionType $question, string $value): string
{
if (!$question->allowsValues())
return strval($question->getNiceValue($value));
$labels = [];
foreach (explode(ExtraQuestionType::QuestionChoicesCharSeparator, $value) as $value_id) {
$label = $question->getNiceValue(trim($value_id));
if (empty($label)) continue;
$labels[] = $label;
}
return implode(ISummitOrderService::ExtraQuestionValueSeparator, $labels);
}
private static function formatAnswerValue(SummitOrderExtraQuestionType $question, string $value): string
{
if (!$question->allowsValues())
return strval($question->getNiceValue($value));
$labels = [];
foreach (explode(ExtraQuestionType::QuestionChoicesCharSeparator, $value) as $value_id) {
$label = $question->getNiceValue(trim($value_id));
if ($label === null || $label === '') continue;
$labels[] = $label;
}
return implode(ISummitOrderService::ExtraQuestionValueSeparator, $labels);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/ModelSerializers/Summit/Registration/SummitAttendeeTicketCSVSerializer.php`
around lines 159 - 171, In formatAnswerValue, the current empty($label) check
will incorrectly drop a legitimate label of "0" from exported CSV values.
Replace that loose emptiness check with a strict null/empty-string validation so
only missing labels are skipped while preserving valid "0" labels. Keep the rest
of the label collection logic in
SummitAttendeeTicketCSVSerializer::formatAnswerValue unchanged.

}
6 changes: 5 additions & 1 deletion app/Services/Model/ISummitOrderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@
*/
interface ISummitOrderService extends IProcessPaymentService
{
// ticket data import csv column naming convention for order extra question answers
// ticket data csv column naming convention: additive per-name column families are namespaced
// so their names can never collide ( import still accepts the bare badge feature name, for legacy csvs )
const ExtraQuestionColumnPrefix = 'extra_question:';
const BadgeFeatureColumnPrefix = 'badge_feature:';
// separator for multi-value extra-question csv cells, shared by import and export ( round-trip )
const ExtraQuestionValueSeparator = '|';

/**
* @param Member|null $owner
Expand Down
Loading
Loading