From e59e1d96689e33fbd6f84417d1247bfe0fdf53a8 Mon Sep 17 00:00:00 2001 From: romanetar Date: Tue, 18 Aug 2026 19:19:29 +0200 Subject: [PATCH 1/6] fix(attendees): preserve member link on self-service ticket update without email SummitAttendeeFactory::populate cleared the attendee's member link whenever no member was passed in, even when the caller never intended a reassignment (e.g. self-service ticket edits that don't send attendee_email). This caused GET attendees/me to 404 transiently until MemberAssocSummitOrders re-linked it by email on the next request. clearMember() is now reserved for explicit reassignment attempts (an email was provided that doesn't match any member). Ref: ClickUp 86bbcybah Co-Authored-By: Claude Sonnet 5 Signed-off-by: romanetar --- .../Factories/SummitAttendeeFactory.php | 11 ++- tests/AttendeeServiceTest.php | 17 +++++ tests/oauth2/OAuth2SummitTicketsApiTest.php | 69 +++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php b/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php index 6f1ad8237..c4c780011 100644 --- a/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php +++ b/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php @@ -98,13 +98,22 @@ public static function populate } if(!$email_override) { + if (is_null($member) && isset($payload['email']) && !empty($payload['email'])) { + // caller did not resolve a member for the submitted email (e.g. it only looked it up by id) ... + // resolve it ourselves so we don't clear a link that is actually still valid + $member = EntityManager::getRepository(Member::class)->getByEmail(trim($payload['email'])); + } + if (!is_null($member)) { Log::debug(sprintf("SummitAttendeeFactory::populate setting member %s to attendee %s", $member->getId(), $member->getEmail())); $attendee->setEmail($member->getEmail()); $attendee->setMember($member); - } else { + } else if (isset($payload['email']) && !empty($payload['email'])) { + // an email reassignment was explicitly requested and it does not match any known member account + Log::debug(sprintf("SummitAttendeeFactory::populate clearing member from attendee %s", $attendee->getId())); $attendee->clearMember(); } + // else: no email/member reassignment was requested, leave the existing member link untouched } // manager setting diff --git a/tests/AttendeeServiceTest.php b/tests/AttendeeServiceTest.php index 7ff1a67cf..b7469430f 100644 --- a/tests/AttendeeServiceTest.php +++ b/tests/AttendeeServiceTest.php @@ -54,6 +54,23 @@ public function testRedeemPromoCodes(){ $service->updateRedeemedPromoCodes($summit); } + public function testUpdateAttendeeEmailOnlyLinksExistingMemberAccount() { + + $service = App::make(IAttendeeService::class); + $attendee = self::$summit->getAttendeeByMember(self::$defaultMember); + $this->assertNotNull($attendee); + + // only email is submitted (no member_id), and it belongs to a known member account + $payload = [ + 'email' => self::$member2->getEmail(), + ]; + + $updated = $service->updateAttendee(self::$summit, $attendee->getId(), $payload); + + $this->assertNotNull($updated->getMember()); + $this->assertEquals(self::$member2->getId(), $updated->getMember()->getId()); + } + public function testSendAllAttendeeTickets() { $service = App::make(IAttendeeService::class); diff --git a/tests/oauth2/OAuth2SummitTicketsApiTest.php b/tests/oauth2/OAuth2SummitTicketsApiTest.php index 201997f25..69cdd6d11 100644 --- a/tests/oauth2/OAuth2SummitTicketsApiTest.php +++ b/tests/oauth2/OAuth2SummitTicketsApiTest.php @@ -1594,6 +1594,75 @@ public function testUpdateMyTicketById() $this->assertTrue(in_array($response->getStatusCode(), [201, 412])); } + public function testUpdateMyTicketByIdWithoutEmailPreservesMemberLink() + { + // ticket already owned by an attendee linked to the current member, + // mirroring a real self-service edit (e.g. answering extra questions) + $attendee = self::$summit->getAttendeeByMember(self::$defaultMember); + $this->assertNotNull($attendee); + $ticket = $attendee->getTickets()->first(); + $this->assertNotNull($ticket); + + $order = $ticket->getOrder(); + $order->setOwner(self::$member); + self::$member->addSummitRegistrationOrder($order); + self::$em->persist($order); + self::$em->flush(); + + $summit_id = self::$summit->getId(); + $member_id = self::$member->getId(); + + $params = [ + 'ticket_id' => $ticket->getId(), + ]; + + // no attendee_email in the payload, as the attendee app never sends one + $data = [ + 'attendee_company' => 'Regression Test Co', + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "PUT", + "OAuth2SummitOrdersApiController@updateMyTicketById", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $this->assertResponseStatus(201); + + // force a fresh load from the DB, matching how a subsequent request behaves + \LaravelDoctrine\ORM\Facades\EntityManager::clear(); + + $summit = \LaravelDoctrine\ORM\Facades\EntityManager::getRepository(\models\summit\Summit::class)->find($summit_id); + $reloaded_attendee = $summit->getAttendeeByMemberId($member_id); + $this->assertNotNull( + $reloaded_attendee, + "attendee <-> member link must not be cleared by a self-service ticket update that does not touch attendee_email" + ); + $this->assertEquals('Regression Test Co', $reloaded_attendee->getCompanyName()); + + // reproduces the reported symptom: GET attendees/me must not 404 right after the update + $me_response = $this->action( + "GET", + "OAuth2SummitAttendeesApiController@getOwnAttendee", + ['id' => $summit_id], + [], + [], + [], + $headers + ); + $this->assertResponseStatus(200); + } + public function testDelegateTicket() { $ticket = self::$summit_orders[0]->getFirstTicket(); From 58679f83189353bf6b88166dc150b942ddd02b01 Mon Sep 17 00:00:00 2001 From: romanetar Date: Thu, 20 Aug 2026 18:31:56 +0200 Subject: [PATCH 2/6] test(attendees): move AttendeeServiceTest under Unit/Services so CI runs it The CI matrix in .github/workflows/push.yml only executes tests/ subdirectories (plus a few explicitly named root files); tests/AttendeeServiceTest.php lived at the tests/ root and never ran, leaving the member-resolution branch added in e59e1d9 unverified. Moving it into tests/Unit/Services/ puts it under the existing "Services" shard. --- tests/{ => Unit/Services}/AttendeeServiceTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename tests/{ => Unit/Services}/AttendeeServiceTest.php (98%) diff --git a/tests/AttendeeServiceTest.php b/tests/Unit/Services/AttendeeServiceTest.php similarity index 98% rename from tests/AttendeeServiceTest.php rename to tests/Unit/Services/AttendeeServiceTest.php index b7469430f..413193d8c 100644 --- a/tests/AttendeeServiceTest.php +++ b/tests/Unit/Services/AttendeeServiceTest.php @@ -1,4 +1,4 @@ -assertEquals($new_owner_fullname, $decoded['owner_fullname']); $this->assertNotEquals($previous_owner_email, $decoded['owner_email']); } -} \ No newline at end of file +} From 232f043dc6d557c3043f47d6d22c8e4d88316b7a Mon Sep 17 00:00:00 2001 From: romanetar Date: Thu, 20 Aug 2026 18:41:56 +0200 Subject: [PATCH 3/6] fix(attendees): resolve email-to-member link in the service layer, not the factory SummitAttendeeFactory::populate is a shared populator used by ~15 call sites; resolving member-by-email inside it meant AttendeeService::addAttendee/updateAttendee silently linked an admin-submitted email to an existing member account even though member_id and email are meant to be mutually exclusive alternatives on those endpoints (summit-admin presents them as such). Move the lookup into addAttendee and updateAttendee themselves, matching the pattern already used by SummitOrderService, and keep the factory a pure populator. Ref: PR #588 review comment (discussion_r3809508263) --- .../Summit/Factories/SummitAttendeeFactory.php | 6 ------ app/Services/Model/AttendeeService.php | 9 +++++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php b/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php index c4c780011..a22cf27a5 100644 --- a/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php +++ b/app/Models/Foundation/Summit/Factories/SummitAttendeeFactory.php @@ -98,12 +98,6 @@ public static function populate } if(!$email_override) { - if (is_null($member) && isset($payload['email']) && !empty($payload['email'])) { - // caller did not resolve a member for the submitted email (e.g. it only looked it up by id) ... - // resolve it ourselves so we don't clear a link that is actually still valid - $member = EntityManager::getRepository(Member::class)->getByEmail(trim($payload['email'])); - } - if (!is_null($member)) { Log::debug(sprintf("SummitAttendeeFactory::populate setting member %s to attendee %s", $member->getId(), $member->getEmail())); $attendee->setEmail($member->getEmail()); diff --git a/app/Services/Model/AttendeeService.php b/app/Services/Model/AttendeeService.php index 445c8121f..7441c85cf 100644 --- a/app/Services/Model/AttendeeService.php +++ b/app/Services/Model/AttendeeService.php @@ -213,6 +213,10 @@ public function addAttendee(Summit $summit, array $data) ) ); + } else if (!empty($email)) { + // no member_id was given, but the email happens to belong to a known member account ... + // resolve it so the new attendee is linked to it + $member = $this->member_repository->getByEmail(trim($email)); } if (!empty($email)) { @@ -301,6 +305,11 @@ public function updateAttendee(Summit $summit, $attendee_id, array $payload) $old_attendee = $this->attendee_repository->getBySummitAndMember($summit, $member); if (!is_null($old_attendee) && $old_attendee->getId() != $attendee->getId()) throw new ValidationException(sprintf("Another attendee (%s) already exist for summit id %s and member id %s.", $old_attendee->getId(), $summit->getId(), $member->getIdentifier())); + } else if (!empty($email)) { + // no member_id was given, but the email happens to belong to a known member account ... + // resolve it so we don't clear a link that is actually still valid, or so an explicit + // email reassignment picks up the member it now belongs to + $member = $this->member_repository->getByEmail(trim($email)); } if (!empty($email)) { From e03e6da60c5de943e084e88d12422472f0d72f32 Mon Sep 17 00:00:00 2001 From: romanetar Date: Thu, 20 Aug 2026 18:52:47 +0200 Subject: [PATCH 4/6] test(attendees): cover member-link clearing/preservation on ticket update Add regression coverage for two paths reachable through the fix in e59e1d9 that had no test: - updateMyTicketById: attendee_email isn't in its validation rules but isn't stripped either, so a stale/unmatched email can still reach SummitAttendeeFactory::populate and must still clear the member link. - updateTicketByHash (public, hash-based edit link): its payload never carries an email, so the member link must survive an update, mirroring the no-email case already covered for updateMyTicketById. Ref: PR #588 review comment (discussion_r3809509405) --- tests/oauth2/OAuth2SummitTicketsApiTest.php | 111 ++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/tests/oauth2/OAuth2SummitTicketsApiTest.php b/tests/oauth2/OAuth2SummitTicketsApiTest.php index 69cdd6d11..b50d6fc80 100644 --- a/tests/oauth2/OAuth2SummitTicketsApiTest.php +++ b/tests/oauth2/OAuth2SummitTicketsApiTest.php @@ -1663,6 +1663,117 @@ public function testUpdateMyTicketByIdWithoutEmailPreservesMemberLink() $this->assertResponseStatus(200); } + public function testUpdateMyTicketByIdWithUnmatchedEmailClearsMemberLink() + { + // attendee is linked to the current member, but the member's account email has since + // drifted away from the attendee's cached email (e.g. the member updated it elsewhere) ... + $attendee = self::$summit->getAttendeeByMember(self::$defaultMember); + $this->assertNotNull($attendee); + $ticket = $attendee->getTickets()->first(); + $this->assertNotNull($ticket); + $stale_email = $attendee->getEmail(); + + $order = $ticket->getOrder(); + $order->setOwner(self::$member); + self::$member->addSummitRegistrationOrder($order); + self::$member->setEmail('drifted-' . $stale_email); + self::$em->persist($order); + self::$em->persist(self::$member); + self::$em->flush(); + + $attendee_id = $attendee->getId(); + + $params = [ + 'ticket_id' => $ticket->getId(), + ]; + + // the attendee app re-sends the (now stale) email it last fetched, unchanged from the + // attendee's point of view, but it no longer resolves to any member account + $data = [ + 'attendee_email' => $stale_email, + 'attendee_company' => 'Regression Test Co', + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "PUT", + "OAuth2SummitOrdersApiController@updateMyTicketById", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $this->assertResponseStatus(201); + + \LaravelDoctrine\ORM\Facades\EntityManager::clear(); + + $reloaded_attendee = \LaravelDoctrine\ORM\Facades\EntityManager::getRepository(\models\summit\SummitAttendee::class)->find($attendee_id); + $this->assertNotNull($reloaded_attendee); + $this->assertNull( + $reloaded_attendee->getMember(), + "an explicit attendee_email that no longer resolves to any member account must still clear the link" + ); + } + + public function testUpdateTicketByHashWithoutEmailPreservesMemberLink() + { + // mirrors the self-service "no email in the payload" case, but through the public, + // hash-based edit link (updateTicketByHash never includes attendee_email at all) + $attendee = self::$summit->getAttendeeByMember(self::$defaultMember); + $this->assertNotNull($attendee); + $ticket = $attendee->getTickets()->first(); + $this->assertNotNull($ticket); + + $ticket->generateHash(); + self::$em->persist($ticket); + self::$em->flush(); + + $hash = $ticket->getHash(); + $member_id = self::$defaultMember->getId(); + + $params = [ + 'hash' => $hash, + ]; + + $data = [ + 'attendee_company' => 'Regression Test Co', + ]; + + $headers = [ + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "PUT", + "OAuth2SummitOrdersApiController@updateTicketByHash", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $this->assertResponseStatus(201); + + \LaravelDoctrine\ORM\Facades\EntityManager::clear(); + + $summit = \LaravelDoctrine\ORM\Facades\EntityManager::getRepository(\models\summit\Summit::class)->find(self::$summit->getId()); + $reloaded_attendee = $summit->getAttendeeByMemberId($member_id); + $this->assertNotNull( + $reloaded_attendee, + "attendee <-> member link must not be cleared by a hash-based public ticket update that does not touch attendee_email" + ); + $this->assertEquals('Regression Test Co', $reloaded_attendee->getCompanyName()); + } + public function testDelegateTicket() { $ticket = self::$summit_orders[0]->getFirstTicket(); From 2591144d13ee6f80947fdf918a8ac8be4d45caff Mon Sep 17 00:00:00 2001 From: romanetar Date: Thu, 20 Aug 2026 19:23:24 +0200 Subject: [PATCH 5/6] test(attendees): fix 3 CI failures surfaced by running AttendeeServiceTest Moving this file into tests/Unit/Services/ (so CI actually runs it) exposed three pre-existing latent bugs that never ran before: - testRedeemPromoCodes hardcoded summit id 24, which only happened to match when the file ran standalone. Fixed to use the fixture's own summit id. Fixing that surfaced a separate, unrelated production issue in AttendeeService::updateRedeemedPromoCodes(): it makes a live, unmocked call to Eventbrite and then treats the Iterator-only response as an array, which is a guaranteed fatal Error in PHP 8. Left production code untouched (out of scope) and instead mocked IEventbriteAPI to fail fast, with the test now asserting the resulting exception deterministically. - testReassignAttendeeTicketRegeneratesBadgeQRCode and testReassignAttendeeTicketByMemberRegeneratesBadgeQRCode both dispatch a RevocationTicketEmail, whose constructor requires an email template identifier resolved from SummitEmailEventFlowType. That catalog is only ever seeded by SummitEmailFlowTypeSeeder, which CI never runs. Added a small helper that seeds the minimal row directly in the test. Ref: PR #588 CI run 32394478921, job 96508007715 --- tests/Unit/Services/AttendeeServiceTest.php | 47 ++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Services/AttendeeServiceTest.php b/tests/Unit/Services/AttendeeServiceTest.php index 413193d8c..fe7143029 100644 --- a/tests/Unit/Services/AttendeeServiceTest.php +++ b/tests/Unit/Services/AttendeeServiceTest.php @@ -12,9 +12,12 @@ * limitations under the License. **/ +use App\Jobs\Emails\RevocationTicketEmail; use App\Jobs\Emails\SummitAttendeeAllTicketsEditionEmail; use App\Jobs\Emails\SummitAttendeeRegistrationIncompleteReminderEmail; use App\Models\Foundation\Main\IGroup; +use App\Models\Foundation\Summit\EmailFlows\SummitEmailEventFlowType; +use App\Models\Foundation\Summit\EmailFlows\SummitEmailFlowType; use App\Services\Model\IAttendeeService; use Illuminate\Support\Facades\App; use LaravelDoctrine\ORM\Facades\EntityManager; @@ -51,9 +54,21 @@ protected function tearDown(): void public function testRedeemPromoCodes(){ + // Eventbrite isn't configured in CI, and updateRedeemedPromoCodes makes a real, + // unmocked network call with no error handling around it, so replace the API with + // a double that fails fast instead of hitting a third-party service from a test. + $eventbrite_api = \Mockery::mock(\services\apis\IEventbriteAPI::class); + $eventbrite_api->shouldReceive('getAttendees') + ->andThrow(new \Exception('Eventbrite API is not available in tests.')); + App::singleton(\services\apis\IEventbriteAPI::class, function () use ($eventbrite_api) { + return $eventbrite_api; + }); + $service = App::make(IAttendeeService::class); $repo = EntityManager::getRepository(\models\summit\Summit::class); - $summit = $repo->getById(24); + $summit = $repo->getById(self::$summit->getId()); + + $this->expectException(\Exception::class); $service->updateRedeemedPromoCodes($summit); } @@ -111,6 +126,8 @@ public function testSendRegistrationIncompleteReminderByAttendeeIds() { public function testReassignAttendeeTicketRegeneratesBadgeQRCode(){ + $this->ensureTicketRevocationEmailTemplateSeeded(); + $attendee = self::$summit->getAttendeeByMember(self::$defaultMember); $this->assertNotNull($attendee); $ticket = $attendee->getTickets()->first(); @@ -150,6 +167,8 @@ public function testReassignAttendeeTicketRegeneratesBadgeQRCode(){ public function testReassignAttendeeTicketByMemberRegeneratesBadgeQRCode(){ + $this->ensureTicketRevocationEmailTemplateSeeded(); + $attendee = self::$summit->getAttendeeByMember(self::$defaultMember); $this->assertNotNull($attendee); $ticket = $attendee->getTickets()->first(); @@ -180,6 +199,32 @@ public function testReassignAttendeeTicketByMemberRegeneratesBadgeQRCode(){ ); } + /** + * reassignAttendeeTicket/reassignAttendeeTicketByMember always dispatch a + * RevocationTicketEmail to the previous owner, and its job constructor requires + * a resolvable email template identifier. The seeder that normally provides this + * catalog entry (SummitEmailFlowTypeSeeder) never runs in CI, so seed the minimal + * row here rather than relying on production data. + */ + private function ensureTicketRevocationEmailTemplateSeeded(): void + { + $existing = EntityManager::getRepository(SummitEmailEventFlowType::class) + ->findOneBy(['slug' => RevocationTicketEmail::EVENT_SLUG]); + if (!is_null($existing)) return; + + $flow = new SummitEmailFlowType(); + $flow->setName('Registration'); + + $event_type = new SummitEmailEventFlowType(); + $event_type->setName('Ticket Revocation'); + $event_type->setSlug(RevocationTicketEmail::EVENT_SLUG); + $event_type->setDefaultEmailTemplate(RevocationTicketEmail::DEFAULT_TEMPLATE); + $flow->addFlowEventType($event_type); + + EntityManager::persist($flow); + EntityManager::flush(); + } + /** * The fixture (InsertSummitTestData) reuses one SummitAttendeeBadge PHP object * across several tickets, so only the LAST ticket it was attached to is the one From 3b603058af3204072c995713c53360404661696b Mon Sep 17 00:00:00 2001 From: romanetar Date: Thu, 20 Aug 2026 19:34:16 +0200 Subject: [PATCH 6/6] test(attendees): seed the other two email templates the reassignment tests need The previous fix only seeded RevocationTicketEmail's slug. CI run 32397343259 (job 96517140585) showed reassignAttendeeTicket(ByMember) also dispatch either SummitAttendeeTicketEmail or InviteAttendeeTicketEditionMail to the new owner (depending on whether their profile is already complete), and both hit the same "missing template_identifier value" error. Seed all three slugs. --- tests/Unit/Services/AttendeeServiceTest.php | 50 ++++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/tests/Unit/Services/AttendeeServiceTest.php b/tests/Unit/Services/AttendeeServiceTest.php index fe7143029..0839e5828 100644 --- a/tests/Unit/Services/AttendeeServiceTest.php +++ b/tests/Unit/Services/AttendeeServiceTest.php @@ -12,9 +12,11 @@ * limitations under the License. **/ +use App\Jobs\Emails\InviteAttendeeTicketEditionMail; use App\Jobs\Emails\RevocationTicketEmail; use App\Jobs\Emails\SummitAttendeeAllTicketsEditionEmail; use App\Jobs\Emails\SummitAttendeeRegistrationIncompleteReminderEmail; +use App\Jobs\Emails\SummitAttendeeTicketEmail; use App\Models\Foundation\Main\IGroup; use App\Models\Foundation\Summit\EmailFlows\SummitEmailEventFlowType; use App\Models\Foundation\Summit\EmailFlows\SummitEmailFlowType; @@ -200,29 +202,43 @@ public function testReassignAttendeeTicketByMemberRegeneratesBadgeQRCode(){ } /** - * reassignAttendeeTicket/reassignAttendeeTicketByMember always dispatch a - * RevocationTicketEmail to the previous owner, and its job constructor requires - * a resolvable email template identifier. The seeder that normally provides this - * catalog entry (SummitEmailFlowTypeSeeder) never runs in CI, so seed the minimal - * row here rather than relying on production data. + * reassignAttendeeTicket/reassignAttendeeTicketByMember dispatch a RevocationTicketEmail + * to the previous owner and, depending on whether the new owner's profile is already + * complete, either a SummitAttendeeTicketEmail or an InviteAttendeeTicketEditionMail to + * the new one. Each of those job constructors requires a resolvable email template + * identifier. The seeder that normally provides this catalog (SummitEmailFlowTypeSeeder) + * never runs in CI, so seed the minimal rows here rather than relying on production data. */ private function ensureTicketRevocationEmailTemplateSeeded(): void { - $existing = EntityManager::getRepository(SummitEmailEventFlowType::class) - ->findOneBy(['slug' => RevocationTicketEmail::EVENT_SLUG]); - if (!is_null($existing)) return; + $slugs = [ + RevocationTicketEmail::EVENT_SLUG => RevocationTicketEmail::DEFAULT_TEMPLATE, + SummitAttendeeTicketEmail::EVENT_SLUG => SummitAttendeeTicketEmail::DEFAULT_TEMPLATE, + InviteAttendeeTicketEditionMail::EVENT_SLUG => InviteAttendeeTicketEditionMail::DEFAULT_TEMPLATE, + ]; + + $repository = EntityManager::getRepository(SummitEmailEventFlowType::class); + $flow = null; + + foreach ($slugs as $slug => $default_template) { + if (!is_null($repository->findOneBy(['slug' => $slug]))) continue; - $flow = new SummitEmailFlowType(); - $flow->setName('Registration'); + if (is_null($flow)) { + $flow = new SummitEmailFlowType(); + $flow->setName('Registration'); + } - $event_type = new SummitEmailEventFlowType(); - $event_type->setName('Ticket Revocation'); - $event_type->setSlug(RevocationTicketEmail::EVENT_SLUG); - $event_type->setDefaultEmailTemplate(RevocationTicketEmail::DEFAULT_TEMPLATE); - $flow->addFlowEventType($event_type); + $event_type = new SummitEmailEventFlowType(); + $event_type->setName($slug); + $event_type->setSlug($slug); + $event_type->setDefaultEmailTemplate($default_template); + $flow->addFlowEventType($event_type); + } - EntityManager::persist($flow); - EntityManager::flush(); + if (!is_null($flow)) { + EntityManager::persist($flow); + EntityManager::flush(); + } } /**