diff --git a/backend/app/Services/Domain/Event/CreateEventService.php b/backend/app/Services/Domain/Event/CreateEventService.php index dbc23330..6c743a4a 100644 --- a/backend/app/Services/Domain/Event/CreateEventService.php +++ b/backend/app/Services/Domain/Event/CreateEventService.php @@ -13,6 +13,7 @@ use HiEvents\Repository\Interfaces\EventSettingsRepositoryInterface; use HiEvents\Repository\Interfaces\EventStatisticRepositoryInterface; use HiEvents\Repository\Interfaces\OrganizerRepositoryInterface; +use HTMLPurifier; use Illuminate\Database\DatabaseManager; use Throwable; @@ -24,6 +25,7 @@ public function __construct( private readonly OrganizerRepositoryInterface $organizerRepository, private readonly DatabaseManager $databaseManager, private readonly EventStatisticRepositoryInterface $eventStatisticsRepository, + private readonly HTMLPurifier $purifier, ) { } @@ -86,7 +88,7 @@ private function handleEventCreate(EventDomainObject $eventData): EventDomainObj 'end_date' => $eventData->getEndDate() ? DateHelper::convertToUTC($eventData->getEndDate(), $eventData->getTimezone()) : null, - 'description' => $eventData->getDescription(), + 'description' => $this->purifier->purify($eventData->getDescription()), 'timezone' => $eventData->getTimezone(), 'currency' => $eventData->getCurrency(), 'location_details' => $eventData->getLocationDetails(), diff --git a/backend/app/Services/Domain/Event/DuplicateEventService.php b/backend/app/Services/Domain/Event/DuplicateEventService.php index 8ee07d13..6cd2db6b 100644 --- a/backend/app/Services/Domain/Event/DuplicateEventService.php +++ b/backend/app/Services/Domain/Event/DuplicateEventService.php @@ -15,6 +15,7 @@ use HiEvents\Services\Domain\PromoCode\CreatePromoCodeService; use HiEvents\Services\Domain\Question\CreateQuestionService; use HiEvents\Services\Domain\Ticket\CreateTicketService; +use HTMLPurifier; use Illuminate\Database\DatabaseManager; use Throwable; @@ -27,6 +28,7 @@ public function __construct( private readonly CreateQuestionService $createQuestionService, private readonly CreatePromoCodeService $createPromoCodeService, private readonly DatabaseManager $databaseManager, + private readonly HTMLPurifier $purifier, ) { } @@ -55,7 +57,7 @@ public function duplicateEvent( ->setTitle($title) ->setStartDate($startDate) ->setEndDate($endDate) - ->setDescription($description) + ->setDescription($this->purifier->purify($description)) ->setStatus(EventStatus::DRAFT->name); $newEvent = $this->cloneExistingEvent( diff --git a/backend/app/Services/Handlers/Event/UpdateEventHandler.php b/backend/app/Services/Handlers/Event/UpdateEventHandler.php index 7e764a6d..f872725a 100644 --- a/backend/app/Services/Handlers/Event/UpdateEventHandler.php +++ b/backend/app/Services/Handlers/Event/UpdateEventHandler.php @@ -11,6 +11,7 @@ use HiEvents\Repository\Interfaces\EventRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; use HiEvents\Services\Handlers\Event\DTO\UpdateEventDTO; +use HTMLPurifier; use Illuminate\Database\DatabaseManager; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Throwable; @@ -22,6 +23,7 @@ public function __construct( private Dispatcher $dispatcher, private DatabaseManager $databaseManager, private OrderRepositoryInterface $orderRepository, + private HTMLPurifier $purifier, ) { } @@ -72,7 +74,7 @@ private function updateEventAttributes(UpdateEventDTO $eventData): void 'end_date' => $eventData->end_date ? DateHelper::convertToUTC($eventData->end_date, $eventData->timezone) : null, - 'description' => $eventData->description, + 'description' => $this->purifier->purify($eventData->description), 'timezone' => $eventData->timezone ?? $existingEvent->getTimezone(), 'currency' => $eventData->currency ?? $existingEvent->getCurrency(), 'location' => $eventData->location, diff --git a/backend/tests/Unit/Services/Domain/Event/CreateEventServiceTest.php b/backend/tests/Unit/Services/Domain/Event/CreateEventServiceTest.php index ac794630..fece1a14 100644 --- a/backend/tests/Unit/Services/Domain/Event/CreateEventServiceTest.php +++ b/backend/tests/Unit/Services/Domain/Event/CreateEventServiceTest.php @@ -12,6 +12,7 @@ use HiEvents\Repository\Interfaces\EventStatisticRepositoryInterface; use HiEvents\Repository\Interfaces\OrganizerRepositoryInterface; use HiEvents\Services\Domain\Event\CreateEventService; +use HTMLPurifier; use Illuminate\Database\DatabaseManager; use Mockery; use Tests\TestCase; @@ -24,6 +25,7 @@ class CreateEventServiceTest extends TestCase private OrganizerRepositoryInterface $organizerRepository; private DatabaseManager $databaseManager; private EventStatisticRepositoryInterface $eventStatisticsRepository; + private HTMLPurifier $purifier; protected function setUp(): void { @@ -34,13 +36,15 @@ protected function setUp(): void $this->organizerRepository = Mockery::mock(OrganizerRepositoryInterface::class); $this->databaseManager = Mockery::mock(DatabaseManager::class); $this->eventStatisticsRepository = Mockery::mock(EventStatisticRepositoryInterface::class); + $this->purifier = Mockery::mock(HTMLPurifier::class); $this->createEventService = new CreateEventService( $this->eventRepository, $this->eventSettingsRepository, $this->organizerRepository, $this->databaseManager, - $this->eventStatisticsRepository + $this->eventStatisticsRepository, + $this->purifier, ); } @@ -86,6 +90,9 @@ public function testCreateEventSuccess(): void $arg['sales_total_gross'] === 0; })); + + $this->purifier->shouldReceive('purify')->andReturn('Test Description'); + $result = $this->createEventService->createEvent($eventData, $eventSettings); $this->assertInstanceOf(EventDomainObject::class, $result); @@ -103,6 +110,8 @@ public function testCreateEventWithoutEventSettings(): void $this->organizerRepository->shouldReceive('findFirstWhere')->andReturn($organizer); $this->eventRepository->shouldReceive('create')->andReturn($eventData); + $this->purifier->shouldReceive('purify')->andReturn('Test Description'); + $this->eventSettingsRepository->shouldReceive('create') ->with(Mockery::on(function ($arg) use ($eventData, $organizer) { return $arg['event_id'] === $eventData->getId() && diff --git a/frontend/src/components/routes/ticket-widget/OrderSummaryAndTickets/index.tsx b/frontend/src/components/routes/ticket-widget/OrderSummaryAndTickets/index.tsx index 03378db8..8374248f 100644 --- a/frontend/src/components/routes/ticket-widget/OrderSummaryAndTickets/index.tsx +++ b/frontend/src/components/routes/ticket-widget/OrderSummaryAndTickets/index.tsx @@ -7,7 +7,7 @@ import {LoadingMask} from "../../../common/LoadingMask"; import {Order, Ticket} from "../../../../types.ts"; import {Card} from "../../../common/Card"; import {AttendeeTicket} from "../../../common/AttendeeTicket"; -import {prettyDate} from "../../../../utilites/dates.ts"; +import {dateToBrowserTz} from "../../../../utilites/dates.ts"; import {PoweredByFooter} from "../../../common/PoweredByFooter"; import {Button, Group} from "@mantine/core"; import {IconPrinter} from "@tabler/icons-react"; @@ -70,7 +70,7 @@ export const OrderSummaryAndTickets = () => {
{t`Order Date`}
- {prettyDate(order?.created_at, event?.timezone)} + {dateToBrowserTz(order?.created_at, event?.timezone)}
diff --git a/frontend/src/utilites/dates.ts b/frontend/src/utilites/dates.ts index fb6d076f..26ffbcce 100644 --- a/frontend/src/utilites/dates.ts +++ b/frontend/src/utilites/dates.ts @@ -8,6 +8,7 @@ import relativeTime from "dayjs/plugin/relativeTime"; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; import advanced from 'dayjs/plugin/advancedFormat'; +import {isSsr} from "./helpers.ts"; dayjs.extend(utc); dayjs.extend(relativeTime); @@ -42,3 +43,17 @@ export const utcToTz = (date: undefined | string | Date, tz: string): string | u // eslint-disable-next-line lingui/no-unlocalized-strings return dayjs.utc(date).tz(tz).format('YYYY-MM-DDTHH:mm'); }; + +/** + * Converts a datetime to the user's browser timezone, with a fallback timezone for SSR. + * + * @param date string + * @param fallbackTz string + */ +export const dateToBrowserTz = (date: string, fallbackTz: string): string => { + const userTimezone = !isSsr() + ? Intl.DateTimeFormat().resolvedOptions().timeZone + : fallbackTz; + + return dayjs.utc(date).tz(userTimezone).format('MMM D, YYYY h:mma z'); +};