From 9a5674c71387767d6f94543e08903888d0bb0c16 Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 13 Jun 2019 14:39:42 +0200 Subject: [PATCH 1/8] remove use of eventController.showEvent #588 --- .../api/v2/user/EventApiV2Controller.java | 107 +++++++++++++++--- .../user/ReservationFlowIntegrationTest.java | 14 +-- 2 files changed, 97 insertions(+), 24 deletions(-) diff --git a/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java index 6f5929fc01..abfec4220a 100644 --- a/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java @@ -22,7 +22,6 @@ import alfio.controller.api.v2.model.EventWithAdditionalInfo; import alfio.controller.api.v2.model.EventWithAdditionalInfo.PaymentProxyWithParameters; import alfio.controller.api.v2.model.TicketCategory; -import alfio.controller.decorator.EventDescriptor; import alfio.controller.decorator.SaleableAdditionalService; import alfio.controller.decorator.SaleableTicketCategory; import alfio.controller.form.ReservationForm; @@ -41,17 +40,13 @@ import alfio.model.transaction.PaymentProxy; import alfio.repository.*; import alfio.repository.user.OrganizationRepository; -import alfio.util.CustomResourceBundleMessageSource; -import alfio.util.ErrorsCode; -import alfio.util.MustacheCustomTagInterceptor; -import alfio.util.Validator; +import alfio.util.*; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.ServletWebRequest; @@ -63,6 +58,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; import static alfio.model.PromoCodeDiscount.categoriesOrNull; @@ -91,6 +87,10 @@ public class EventApiV2Controller { private final WaitingQueueManager waitingQueueManager; private final I18nManager i18nManager; private final TicketCategoryRepository ticketCategoryRepository; + private final TicketRepository ticketRepository; + private final TicketReservationManager ticketReservationManager; + private final PromoCodeDiscountRepository promoCodeRepository; + private final EventStatisticsManager eventStatisticsManager; // private final PromoCodeDiscountRepository promoCodeDiscountRepository; @@ -248,16 +248,46 @@ public ResponseEntity> subscribeToWaitingList(@PathVa } @GetMapping("event/{eventName}/ticket-categories") - public ResponseEntity getTicketCategories(@PathVariable("eventName") String eventName, @RequestParam(value = "code", required = false) String code, Model model, HttpServletRequest request) { + public ResponseEntity getTicketCategories(@PathVariable("eventName") String eventName, @RequestParam(value = "code", required = false) String code, HttpServletRequest request) { var appliedPromoCode = applyPromoCodeInRequest(eventName, code, request); - if ("/event/show-event".equals(eventController.showEvent(eventName, model, request, Locale.ENGLISH))) { - var valid = (List) model.asMap().get("ticketCategories"); + // + return eventRepository.findOptionalByShortName(eventName).filter(e -> e.getStatus() != Event.Status.DISABLED).map(event -> { + Optional maybeSpecialCode = SessionUtil.retrieveSpecialPriceCode(request); + Optional specialCode = maybeSpecialCode.flatMap(specialPriceRepository::getByCode); + + Optional promoCodeDiscount = SessionUtil.retrievePromotionCodeDiscount(request) + .flatMap((retrievedCode) -> promoCodeRepository.findPromoCodeInEventOrOrganization(event.getId(), retrievedCode)); + final ZonedDateTime now = ZonedDateTime.now(event.getZoneId()); + //hide access restricted ticket categories + var ticketCategories = ticketCategoryRepository.findAllTicketCategories(event.getId()); + + List saleableTicketCategories = ticketCategories.stream() + .filter((c) -> !c.isAccessRestricted() || shouldDisplayRestrictedCategory(specialCode, c, promoCodeDiscount)) + .map((m) -> { + int maxTickets = configurationManager.getIntConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), m.getId(), ConfigurationKeys.MAX_AMOUNT_OF_TICKETS_BY_RESERVATION), 5); + PromoCodeDiscount filteredPromoCode = promoCodeDiscount.filter(promoCode -> shouldApplyDiscount(promoCode, m)).orElse(null); + if (specialCode.isPresent()) { + maxTickets = Math.min(1, maxTickets); + } else if (filteredPromoCode != null && filteredPromoCode.getMaxUsage() != null) { + maxTickets = filteredPromoCode.getMaxUsage() - promoCodeRepository.countConfirmedPromoCode(filteredPromoCode.getId(), categoriesOrNull(filteredPromoCode), null, categoriesOrNull(filteredPromoCode) != null ? "X" : null); + } + return new SaleableTicketCategory(m, "", + now, event, ticketReservationManager.countAvailableTickets(event, m), maxTickets, + filteredPromoCode); + }) + .collect(Collectors.toList()); + + + var valid = saleableTicketCategories.stream().filter(tc -> !tc.getExpired()).collect(Collectors.toList()); + + // + var ticketCategoryIds = valid.stream().map(SaleableTicketCategory::getId).collect(Collectors.toList()); var ticketCategoryDescriptions = ticketCategoryDescriptionRepository.descriptionsByTicketCategory(ticketCategoryIds); - Event event = ((EventDescriptor) model.asMap().get("event")).getEvent(); + var converted = valid.stream() .map(stc -> { @@ -300,16 +330,17 @@ public ResponseEntity getTicketCategories(@PathVariable("eventN // // waiting queue parameters - boolean displayWaitingQueueForm = (boolean) model.asMap().get("displayWaitingQueueForm"); - boolean preSales = (boolean) model.asMap().get("preSales"); - List unboundedCategories = (List) model.asMap().get("unboundedCategories"); + boolean displayWaitingQueueForm = EventUtil.displayWaitingQueueForm(event, saleableTicketCategories, configurationManager, eventStatisticsManager.noSeatsAvailable()); + boolean preSales = EventUtil.isPreSales(event, saleableTicketCategories); + Predicate waitingQueueTargetCategory = tc -> !tc.getExpired() && !tc.isBounded(); + List unboundedCategories = saleableTicketCategories.stream().filter(waitingQueueTargetCategory).collect(Collectors.toList()); var tcForWaitingList = unboundedCategories.stream().map(stc -> new ItemsByCategory.TicketCategoryForWaitingList(stc.getId(), stc.getName())).collect(toList()); // return new ResponseEntity<>(new ItemsByCategory(converted, additionalServicesRes, displayWaitingQueueForm, preSales, tcForWaitingList), getCorsHeaders(), HttpStatus.OK); - } else { + }).orElseGet(() -> { return ResponseEntity.notFound().headers(getCorsHeaders()).build(); - } + }); } @GetMapping("event/{eventName}/languages") @@ -332,8 +363,31 @@ public void getCalendar(@PathVariable("eventName") String eventName, @PathVariable("locale") String locale, @RequestParam(value = "type", required = false) String calendarType, @RequestParam(value = "ticketId", required = false) String ticketId, - HttpServletResponse response) throws IOException { - eventController.calendar(eventName, locale, calendarType, ticketId, response); + HttpServletResponse response) { + + eventRepository.findOptionalByShortName(eventName).ifPresentOrElse((ev -> { + var description = eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(ev.getId(), EventDescription.EventDescriptionType.DESCRIPTION, locale).orElse(""); + var category = ticketRepository.findOptionalByUUID(ticketId).map(t -> ticketCategoryRepository.getById(t.getCategoryId())).orElse(null); + if ("google".equals(calendarType)) { + try { + response.sendRedirect(EventUtil.getGoogleCalendarURL(ev, category, description)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } else { + EventUtil.getIcalForEvent(ev, category, description).ifPresentOrElse(ical -> { + response.setContentType("text/calendar"); + response.setHeader("Content-Disposition", "inline; filename=\"calendar.ics\""); + try (var os = response.getOutputStream()){ + os.write(ical); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }, () -> { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + }); + } + }), () -> response.setStatus(HttpServletResponse.SC_NOT_FOUND)); } @PostMapping(value = "event/{eventName}/reserve-tickets") @@ -389,6 +443,25 @@ public ResponseEntity> validateCode(@PathVariable(" }).orElseGet(() -> ResponseEntity.notFound().build()); } + + private boolean shouldDisplayRestrictedCategory(Optional specialCode, alfio.model.TicketCategory c, Optional optionalPromoCode) { + if(optionalPromoCode.isPresent()) { + var promoCode = optionalPromoCode.get(); + if(promoCode.getCodeType() == PromoCodeDiscount.CodeType.ACCESS && c.getId() == promoCode.getHiddenCategoryId()) { + return true; + } + } + return specialCode.filter(sc -> sc.getTicketCategoryId() == c.getId()).isPresent(); + } + + private static boolean shouldApplyDiscount(PromoCodeDiscount promoCodeDiscount, alfio.model.TicketCategory ticketCategory) { + if(promoCodeDiscount.getCodeType() == PromoCodeDiscount.CodeType.DISCOUNT) { + return promoCodeDiscount.getCategories().isEmpty() || promoCodeDiscount.getCategories().contains(ticketCategory.getId()); + } + return ticketCategory.isAccessRestricted() && ticketCategory.getId() == promoCodeDiscount.getHiddenCategoryId(); + } + + //TODO: temporary! private Optional, Optional>>> applyPromoCodeInRequest(String eventName, String code, HttpServletRequest request) { return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName).map(event -> { diff --git a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java index 2d9563e093..542a0e2e08 100644 --- a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java @@ -318,9 +318,9 @@ public void reservationFlowTest() throws Exception { // check ticket & all, we have 2 ticket categories, 1 hidden - assertEquals(HttpStatus.NOT_FOUND, eventApiV2Controller.getTicketCategories("NOT_EXISTING", null, new BindingAwareModelMap(), new MockHttpServletRequest()).getStatusCode()); + assertEquals(HttpStatus.NOT_FOUND, eventApiV2Controller.getTicketCategories("NOT_EXISTING", null, new MockHttpServletRequest()).getStatusCode()); { - var itemsRes = eventApiV2Controller.getTicketCategories(event.getShortName(), null, new BindingAwareModelMap(), new MockHttpServletRequest()); + var itemsRes = eventApiV2Controller.getTicketCategories(event.getShortName(), null, new MockHttpServletRequest()); assertEquals(HttpStatus.OK, itemsRes.getStatusCode()); var items = itemsRes.getBody(); @@ -350,7 +350,7 @@ public void reservationFlowTest() throws Exception { var tc = ticketCategoryRepository.getById(visibleCat.getId()); ticketCategoryRepository.fixDates(visibleCat.getId(), tc.getInception(event.getZoneId()).plusDays(2), tc.getExpiration(event.getZoneId())); // - items = eventApiV2Controller.getTicketCategories(event.getShortName(), null, new BindingAwareModelMap(), new MockHttpServletRequest()).getBody(); + items = eventApiV2Controller.getTicketCategories(event.getShortName(), null, new MockHttpServletRequest()).getBody(); assertTrue(items.isWaitingList()); assertTrue(items.isPreSales()); // @@ -390,7 +390,7 @@ public void reservationFlowTest() throws Exception { var hiddenCode = hiddenCodeRes.getBody(); assertEquals(EventCode.EventCodeType.ACCESS, hiddenCode.getValue().getType()); - var itemsRes2 = eventApiV2Controller.getTicketCategories(event.getShortName(), HIDDEN_CODE, new BindingAwareModelMap(), new MockHttpServletRequest()); + var itemsRes2 = eventApiV2Controller.getTicketCategories(event.getShortName(), HIDDEN_CODE, new MockHttpServletRequest()); var items2 = itemsRes2.getBody(); assertEquals(2, items2.getTicketCategories().size()); @@ -409,7 +409,7 @@ public void reservationFlowTest() throws Exception { var discountCodeRes = eventApiV2Controller.validateCode(event.getShortName(), PROMO_CODE); var discountCode = discountCodeRes.getBody(); assertEquals(EventCode.EventCodeType.DISCOUNT, discountCode.getValue().getType()); - var itemsRes3 = eventApiV2Controller.getTicketCategories(event.getShortName(), PROMO_CODE, new BindingAwareModelMap(), new MockHttpServletRequest()); + var itemsRes3 = eventApiV2Controller.getTicketCategories(event.getShortName(), PROMO_CODE, new MockHttpServletRequest()); var items3 = itemsRes3.getBody(); @@ -438,7 +438,7 @@ public void reservationFlowTest() throws Exception { var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); ticketReservation.setAmount(1); - ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(event.getShortName(), null, new BindingAwareModelMap(), new MockHttpServletRequest()).getBody().getTicketCategories().get(0).getId()); + ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(event.getShortName(), null, new MockHttpServletRequest()).getBody().getTicketCategories().get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); var res = eventApiV2Controller.reserveTicket(event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), new RedirectAttributesModelMap()); assertEquals(HttpStatus.OK, res.getStatusCode()); @@ -461,7 +461,7 @@ public void reservationFlowTest() throws Exception { var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); ticketReservation.setAmount(1); - ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(event.getShortName(), null, new BindingAwareModelMap(), new MockHttpServletRequest()).getBody().getTicketCategories().get(0).getId()); + ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(event.getShortName(), null, new MockHttpServletRequest()).getBody().getTicketCategories().get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); var res = eventApiV2Controller.reserveTicket(event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), new RedirectAttributesModelMap()); assertEquals(HttpStatus.OK, res.getStatusCode()); From 0ab3d3ebe726eb1b0555612084044a1d018f42b1 Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 13 Jun 2019 15:02:44 +0200 Subject: [PATCH 2/8] remove use of eventController in event api v2 controller #588 --- .../api/v2/user/EventApiV2Controller.java | 62 ++++++++++++++----- .../user/ReservationFlowIntegrationTest.java | 6 +- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java index abfec4220a..0806253c24 100644 --- a/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java @@ -16,7 +16,6 @@ */ package alfio.controller.api.v2.user; -import alfio.controller.EventController; import alfio.controller.api.v2.model.*; import alfio.controller.api.v2.model.AdditionalService; import alfio.controller.api.v2.model.EventWithAdditionalInfo; @@ -43,6 +42,7 @@ import alfio.util.*; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -50,7 +50,6 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.ServletWebRequest; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -72,7 +71,6 @@ @AllArgsConstructor public class EventApiV2Controller { - private final EventController eventController; private final EventManager eventManager; private final EventRepository eventRepository; private final ConfigurationManager configurationManager; @@ -91,11 +89,9 @@ public class EventApiV2Controller { private final TicketReservationManager ticketReservationManager; private final PromoCodeDiscountRepository promoCodeRepository; private final EventStatisticsManager eventStatisticsManager; - - // + private final RecaptchaService recaptchaService; private final PromoCodeDiscountRepository promoCodeDiscountRepository; private final SpecialPriceRepository specialPriceRepository; - // @GetMapping("events") @@ -395,8 +391,7 @@ public ResponseEntity> reserveTicket(@PathVariable("ev @RequestParam("lang") String lang, @RequestBody ReservationForm reservation, BindingResult bindingResult, - ServletWebRequest request, - RedirectAttributes redirectAttributes) { + ServletWebRequest request) { if(StringUtils.trimToNull(reservation.getPromoCode()) != null) { var codeCheck = applyPromoCodeInRequest(eventName, reservation.getPromoCode(), request.getRequest()); @@ -407,16 +402,44 @@ public ResponseEntity> reserveTicket(@PathVariable("ev }); } - String redirectResult = eventController.reserveTicket(eventName, reservation, bindingResult, request, redirectAttributes, Locale.forLanguageTag(lang)); - if (bindingResult.hasErrors()) { - return new ResponseEntity<>(ValidatedResponse.toResponse(bindingResult, null), getCorsHeaders(), HttpStatus.UNPROCESSABLE_ENTITY); - } else { - String reservationIdentifier = redirectResult - .substring(redirectResult.lastIndexOf("reservation/")+"reservation/".length()) - .replace("/book", ""); - return ResponseEntity.ok(new ValidatedResponse<>(ValidationResult.success(), reservationIdentifier)); - } + Optional>> r = eventRepository.findOptionalByShortName(eventName).map(event -> { + if (isCaptchaInvalid(reservation.getCaptcha(), request.getRequest(), event)) { + bindingResult.reject(ErrorsCode.STEP_2_CAPTCHA_VALIDATION_FAILED); + } + + var reservationIdRes = reservation.validate(bindingResult, ticketReservationManager, additionalServiceRepository, eventManager, event).flatMap(selected -> { + Date expiration = DateUtils.addMinutes(new Date(), ticketReservationManager.getReservationTimeout(event)); + try { + String reservationId = ticketReservationManager.createTicketReservation(event, + selected.getLeft(), selected.getRight(), expiration, + SessionUtil.retrieveSpecialPriceSessionId(request.getRequest()), + SessionUtil.retrievePromotionCodeDiscount(request.getRequest()), + Locale.forLanguageTag(lang), false); + return Optional.of(reservationId); + } catch (TicketReservationManager.NotEnoughTicketsException nete) { + bindingResult.reject(ErrorsCode.STEP_1_NOT_ENOUGH_TICKETS); + } catch (TicketReservationManager.MissingSpecialPriceTokenException missing) { + bindingResult.reject(ErrorsCode.STEP_1_ACCESS_RESTRICTED); + } catch (TicketReservationManager.InvalidSpecialPriceTokenException invalid) { + bindingResult.reject(ErrorsCode.STEP_1_CODE_NOT_FOUND); + SessionUtil.cleanupSession(request.getRequest()); + } catch (TicketReservationManager.TooManyTicketsForDiscountCodeException tooMany) { + bindingResult.reject(ErrorsCode.STEP_2_DISCOUNT_CODE_USAGE_EXCEEDED); + } + + return Optional.empty(); + }); + + if (bindingResult.hasErrors()) { + return new ResponseEntity<>(ValidatedResponse.toResponse(bindingResult, null), getCorsHeaders(), HttpStatus.UNPROCESSABLE_ENTITY); + } else { + var reservationIdentifier = reservationIdRes.orElseThrow(IllegalStateException::new); + return ResponseEntity.ok(new ValidatedResponse<>(ValidationResult.success(), reservationIdentifier)); + } + }); + + return r.orElseGet(() -> ResponseEntity.notFound().build()); } @GetMapping(value = "event/{eventName}/validate-code") @@ -524,4 +547,9 @@ private static HttpHeaders getCorsHeaders() { headers.add("Access-Control-Allow-Origin", "*"); return headers; } + + private boolean isCaptchaInvalid(String recaptchaResponse, HttpServletRequest request, EventAndOrganizationId event) { + return configurationManager.isRecaptchaForTicketSelectionEnabled(event) + && !recaptchaService.checkRecaptcha(recaptchaResponse, request); + } } diff --git a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java index 542a0e2e08..1a4863931b 100644 --- a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java @@ -426,7 +426,7 @@ public void reservationFlowTest() throws Exception { //validation error: select at least one { var form = new ReservationForm(); - var res = eventApiV2Controller.reserveTicket(event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), new RedirectAttributesModelMap()); + var res = eventApiV2Controller.reserveTicket(event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse())); assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, res.getStatusCode()); var resBody = res.getBody(); assertFalse(resBody.isSuccess()); @@ -440,7 +440,7 @@ public void reservationFlowTest() throws Exception { ticketReservation.setAmount(1); ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(event.getShortName(), null, new MockHttpServletRequest()).getBody().getTicketCategories().get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); - var res = eventApiV2Controller.reserveTicket(event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), new RedirectAttributesModelMap()); + var res = eventApiV2Controller.reserveTicket(event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse())); assertEquals(HttpStatus.OK, res.getStatusCode()); var resBody = res.getBody(); assertTrue(resBody.isSuccess()); @@ -463,7 +463,7 @@ public void reservationFlowTest() throws Exception { ticketReservation.setAmount(1); ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(event.getShortName(), null, new MockHttpServletRequest()).getBody().getTicketCategories().get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); - var res = eventApiV2Controller.reserveTicket(event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), new RedirectAttributesModelMap()); + var res = eventApiV2Controller.reserveTicket(event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse())); assertEquals(HttpStatus.OK, res.getStatusCode()); var resBody = res.getBody(); assertTrue(resBody.isSuccess()); From 6a6cc4a861691e6b7ee45a06668bef0971012e19 Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 13 Jun 2019 15:12:07 +0200 Subject: [PATCH 3/8] remove use of ticket controller in ticket api v2 controller #588 --- .../api/v2/user/TicketApiV2Controller.java | 66 ++++++++++++++++--- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java index ff6737ea38..18678d55e7 100644 --- a/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java @@ -16,23 +16,29 @@ */ package alfio.controller.api.v2.user; -import alfio.controller.TicketController; import alfio.controller.api.support.TicketHelper; import alfio.controller.api.v2.model.TicketInfo; import alfio.controller.api.v2.model.ValidatedResponse; import alfio.controller.form.UpdateTicketOwnerForm; import alfio.controller.support.Formatters; +import alfio.controller.support.TemplateProcessor; +import alfio.manager.ExtensionManager; +import alfio.manager.FileUploadManager; +import alfio.manager.NotificationManager; import alfio.manager.TicketReservationManager; import alfio.model.Event; import alfio.model.Ticket; import alfio.model.TicketCategory; import alfio.model.TicketReservation; import alfio.model.transaction.PaymentProxy; +import alfio.model.user.Organization; import alfio.repository.TicketCategoryRepository; +import alfio.repository.user.OrganizationRepository; import alfio.util.CustomResourceBundleMessageSource; import alfio.util.ImageUtil; +import alfio.util.LocaleUtil; +import alfio.util.TemplateManager; import lombok.AllArgsConstructor; -import org.apache.commons.lang3.tuple.Triple; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -42,7 +48,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.OutputStream; import java.time.temporal.ChronoUnit; +import java.util.Locale; import java.util.Optional; @RestController @@ -50,12 +58,15 @@ @RequestMapping("/api/v2/public/") public class TicketApiV2Controller { - - private final TicketController ticketController; private final TicketHelper ticketHelper; private final TicketReservationManager ticketReservationManager; private final TicketCategoryRepository ticketCategoryRepository; private final CustomResourceBundleMessageSource messageSource; + private final ExtensionManager extensionManager; + private final FileUploadManager fileUploadManager; + private final OrganizationRepository organizationRepository; + private final TemplateManager templateManager; + private final NotificationManager notificationManager; @GetMapping("/event/{eventName}/ticket/{ticketIdentifier}/code.png") @@ -82,16 +93,55 @@ public void showQrCode(@PathVariable("eventName") String eventName, @GetMapping("/event/{eventName}/ticket/{ticketIdentifier}/download-ticket") public void generateTicketPdf(@PathVariable("eventName") String eventName, @PathVariable("ticketIdentifier") String ticketIdentifier, - HttpServletRequest request, HttpServletResponse response) throws IOException { - ticketController.generateTicketPdf(eventName, ticketIdentifier, request, response); + HttpServletRequest request, HttpServletResponse response) { + + ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier).ifPresentOrElse(data -> { + + Ticket ticket = data.getRight(); + Event event = data.getLeft(); + TicketReservation ticketReservation = data.getMiddle(); + + response.setContentType("application/pdf"); + response.addHeader("Content-Disposition", "attachment; filename=ticket-" + ticketIdentifier + ".pdf"); + try (OutputStream os = response.getOutputStream()) { + TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId()); + Organization organization = organizationRepository.getById(event.getOrganizationId()); + String reservationID = ticketReservationManager.getShortReservationID(event, ticketReservation); + TemplateProcessor.renderPDFTicket(LocaleUtil.getTicketLanguage(ticket, request), event, ticketReservation, + ticket, ticketCategory, organization, + templateManager, fileUploadManager, + reservationID, os, ticketHelper.buildRetrieveFieldValuesFunction(), extensionManager); + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } + }, () -> { + try { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } + }); } @PostMapping("/event/{eventName}/ticket/{ticketIdentifier}/send-ticket-by-email") public ResponseEntity sendTicketByEmail(@PathVariable("eventName") String eventName, @PathVariable("ticketIdentifier") String ticketIdentifier, HttpServletRequest request) { - var res = ticketController.sendTicketByEmail(eventName, ticketIdentifier, request); - return "OK".equals(res) ? ResponseEntity.ok(true) : ResponseEntity.notFound().build(); + + return ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier).map(data -> { + Ticket ticket = data.getRight(); + Event event = data.getLeft(); + Locale locale = LocaleUtil.getTicketLanguage(ticket, request); + + TicketReservation reservation = data.getMiddle(); + Organization organization = organizationRepository.getById(event.getOrganizationId()); + TicketCategory category = ticketCategoryRepository.getById(ticket.getCategoryId()); + notificationManager.sendTicketByEmail(ticket, + event, locale, TemplateProcessor.buildPartialEmail(event, organization, reservation, category, templateManager, ticketReservationManager.ticketUpdateUrl(event, ticket.getUuid()), request), + reservation, ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId())); + return ResponseEntity.ok(true); + + }).orElseGet(() -> ResponseEntity.notFound().build()); } @DeleteMapping("/event/{eventName}/ticket/{ticketIdentifier}") From 53742b07ec4f0d81d773c4670e6af67709123a27 Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 13 Jun 2019 15:24:36 +0200 Subject: [PATCH 4/8] remove use of invoice receipt controller and payment api controller in reservation api v2 controller #588 --- .../v2/user/ReservationApiV2Controller.java | 114 +++++++++++++++--- 1 file changed, 96 insertions(+), 18 deletions(-) diff --git a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java index 719c844763..a1b27da090 100644 --- a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java @@ -16,7 +16,6 @@ */ package alfio.controller.api.v2.user; -import alfio.controller.InvoiceReceiptController; import alfio.controller.ReservationController; import alfio.controller.api.v2.model.ReservationInfo; import alfio.controller.api.v2.model.ReservationInfo.TicketsByTicketCategory; @@ -25,27 +24,31 @@ import alfio.controller.api.v2.model.ValidatedResponse; import alfio.controller.form.ContactAndTicketsForm; import alfio.controller.form.PaymentForm; -import alfio.controller.payment.api.PaymentApiController; import alfio.controller.support.SessionUtil; -import alfio.manager.EventManager; -import alfio.manager.PaymentManager; -import alfio.manager.TicketReservationManager; +import alfio.controller.support.TemplateProcessor; +import alfio.manager.*; import alfio.manager.support.PaymentResult; import alfio.manager.system.ConfigurationManager; import alfio.model.*; import alfio.model.system.Configuration; +import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.PaymentProxy; import alfio.model.transaction.PaymentToken; import alfio.model.transaction.TransactionInitializationToken; import alfio.repository.EventRepository; import alfio.repository.TicketFieldRepository; import alfio.repository.TicketReservationRepository; +import alfio.util.FileUtil; +import alfio.util.TemplateManager; +import alfio.util.TemplateResource; import lombok.AllArgsConstructor; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.ui.Model; import org.springframework.util.MultiValueMap; import org.springframework.validation.BindingResult; @@ -55,9 +58,11 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import java.io.IOException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; @@ -75,9 +80,11 @@ public class ReservationApiV2Controller { private final TicketReservationRepository ticketReservationRepository; private final TicketFieldRepository ticketFieldRepository; private final MessageSource messageSource; - private final InvoiceReceiptController invoiceReceiptController; - private final PaymentApiController paymentApiController; private final ConfigurationManager configurationManager; + private final PaymentManager paymentManager; + private final FileUploadManager fileUploadManager; + private final TemplateManager templateManager; + private final ExtensionManager extensionManager; /** * See {@link ReservationController#showBookingPage(String, String, Model, Locale)} @@ -286,12 +293,14 @@ public ResponseEntity reSendReservationConfirmationEmail(@PathVariable( } } + + // @GetMapping("/event/{eventName}/reservation/{reservationId}/receipt") public ResponseEntity getReceipt(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, HttpServletResponse response, Authentication authentication) { - return invoiceReceiptController.getReceipt(eventName, reservationId, response, authentication); + return handleReservationWith(eventName, reservationId, authentication, generatePdfFunction(false, response)); } @GetMapping("/event/{eventName}/reservation/{reservationId}/invoice") @@ -299,9 +308,57 @@ public ResponseEntity getInvoice(@PathVariable("eventName") String eventNa @PathVariable("reservationId") String reservationId, HttpServletResponse response, Authentication authentication) { - return invoiceReceiptController.getInvoice(eventName, reservationId, response, authentication); + return handleReservationWith(eventName, reservationId, authentication, generatePdfFunction(true, response)); + } + // + + private ResponseEntity handleReservationWith(String eventName, String reservationId, Authentication authentication, + BiFunction> with) { + ResponseEntity notFound = ResponseEntity.notFound().build(); + ResponseEntity badRequest = ResponseEntity.badRequest().build(); + + + + return eventRepository.findOptionalByShortName(eventName).map(event -> { + if(canAccessReceiptOrInvoice(event, authentication)) { + return ticketReservationManager.findById(reservationId).map(ticketReservation -> with.apply(event, ticketReservation)).orElse(notFound); + } else { + return badRequest; + } + } + ).orElse(notFound); } + private boolean canAccessReceiptOrInvoice(EventAndOrganizationId event, Authentication authentication) { + return configurationManager.canGenerateReceiptOrInvoiceToCustomer(event) || !isAnonymous(authentication); + } + + + private boolean isAnonymous(Authentication authentication) { + return authentication == null || + authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).anyMatch("ROLE_ANONYMOUS"::equals); + } + + private BiFunction> generatePdfFunction(boolean forInvoice, HttpServletResponse response) { + return (event, reservation) -> { + if(forInvoice ^ reservation.getInvoiceNumber() != null || reservation.isCancelled()) { + return ResponseEntity.notFound().build(); + } + + Map billingModel = ticketReservationManager.getOrCreateBillingDocumentModel(event, reservation, null); + + try { + FileUtil.sendHeaders(response, event.getShortName(), reservation.getId(), forInvoice ? "invoice" : "receipt"); + TemplateProcessor.buildReceiptOrInvoicePdf(event, fileUploadManager, new Locale(reservation.getUserLanguage()), + templateManager, billingModel, forInvoice ? TemplateResource.INVOICE_PDF : TemplateResource.RECEIPT_PDF, + extensionManager, response.getOutputStream()); + return ResponseEntity.ok(null); + } catch (IOException ioe) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + }; + } + //---------------- @PostMapping("/event/{eventName}/reservation/{reservationId}/payment/{method}/init") @@ -309,23 +366,44 @@ public ResponseEntity initTransaction(@PathVaria @PathVariable("reservationId") String reservationId, @PathVariable("method") String paymentMethodStr, @RequestParam MultiValueMap allParams) { - return paymentApiController.initTransaction(eventName, reservationId, paymentMethodStr, allParams); + var paymentMethod = PaymentMethod.safeParse(paymentMethodStr); + + if(paymentMethod == null) { + return ResponseEntity.badRequest().build(); + } + + Optional> responseEntity = getEventReservationPair(eventName, reservationId) + .map(pair -> { + var event = pair.getLeft(); + return ticketReservationManager.initTransaction(event, reservationId, paymentMethod, allParams) + .map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + }); + return responseEntity.orElseGet(() -> ResponseEntity.badRequest().build()); + } + + private Optional> getEventReservationPair(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId) { + return eventRepository.findOptionalByShortName(eventName) + .map(event -> Pair.of(event, ticketReservationManager.findById(reservationId))) + .filter(pair -> pair.getRight().isPresent()) + .map(pair -> Pair.of(pair.getLeft(), pair.getRight().orElseThrow())); } @GetMapping("/event/{eventName}/reservation/{reservationId}/payment/{method}/status") public ResponseEntity getTransactionStatus(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, @PathVariable("method") String paymentMethodStr) { - var res = paymentApiController.getTransactionStatus(eventName, reservationId, paymentMethodStr); - if(res.hasBody()) { - var paymentResult = res.getBody(); - return ResponseEntity.ok(new ReservationPaymentResult(paymentResult.isSuccessful(), - paymentResult.isRedirect(), paymentResult.getRedirectUrl(), - paymentResult.isFailed(), paymentResult.getGatewayIdOrNull())); - } else { - return ResponseEntity.status(res.getStatusCode()).build(); + var paymentMethod = PaymentMethod.safeParse(paymentMethodStr); + + if(paymentMethod == null) { + return ResponseEntity.badRequest().build(); } + + return getEventReservationPair(eventName, reservationId) + .flatMap(pair -> paymentManager.getTransactionStatus(pair.getRight(), paymentMethod)) + .map(pr -> ResponseEntity.ok(new ReservationPaymentResult(pr.isSuccessful(), pr.isRedirect(), pr.getRedirectUrl(), pr.isFailed(), pr.getGatewayIdOrNull()))) + .orElseGet(() -> ResponseEntity.notFound().build()); } From e390a9f03be2407b0670ac6887150306b8f8541b Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 13 Jun 2019 15:33:52 +0200 Subject: [PATCH 5/8] remove use of reservationController.reSendReservationConfirmationEmail in reservation api v2 controller #588 --- .../v2/user/ReservationApiV2Controller.java | 20 ++++++++++++------- .../user/ReservationFlowIntegrationTest.java | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java index a1b27da090..056d356dc6 100644 --- a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java @@ -54,6 +54,7 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import org.springframework.web.servlet.support.RequestContextUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -284,13 +285,18 @@ public ResponseEntity> validateToOverview(@PathVariab @PostMapping("/event/{eventName}/reservation/{reservationId}/re-send-email") public ResponseEntity reSendReservationConfirmationEmail(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, HttpServletRequest request) { - var res = reservationController.reSendReservationConfirmationEmail(eventName, reservationId, request); - if(res.endsWith("confirmation-email-sent=true")) { - return ResponseEntity.ok(true); - } else { - return ResponseEntity.ok(false); - } + @PathVariable("reservationId") String reservationId, + @RequestParam("lang") String lang) { + + var res = eventRepository.findOptionalByShortName(eventName).map(event -> + ticketReservationManager.findById(reservationId).map(ticketReservation -> { + Locale locale = Locale.forLanguageTag(lang); + ticketReservationManager.sendConfirmationEmail(event, ticketReservation, locale); + return true; + }).orElse(false) + ).orElse(false); + + return ResponseEntity.ok(res); } diff --git a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java index 1a4863931b..0cd1a8174d 100644 --- a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java @@ -558,7 +558,7 @@ public void reservationFlowTest() throws Exception { assertFalse(orderSummary.getNotYetPaid()); - var confRes = reservationApiV2Controller.reSendReservationConfirmationEmail(event.getShortName(), reservationId, new MockHttpServletRequest()); + var confRes = reservationApiV2Controller.reSendReservationConfirmationEmail(event.getShortName(), reservationId, "en"); assertEquals(HttpStatus.OK, confRes.getStatusCode()); assertTrue(confRes.getBody()); From 0253715634815ff07d6d432a1b9cf584c4c15e65 Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 13 Jun 2019 19:11:12 +0200 Subject: [PATCH 6/8] remove use of reservationController.validateToOverview in reservation api v2 controller #588 --- .../v2/user/ReservationApiV2Controller.java | 183 ++++++++++++++++-- .../user/ReservationFlowIntegrationTest.java | 8 +- 2 files changed, 172 insertions(+), 19 deletions(-) diff --git a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java index 056d356dc6..0acbd58df5 100644 --- a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java @@ -17,6 +17,7 @@ package alfio.controller.api.v2.user; import alfio.controller.ReservationController; +import alfio.controller.api.support.TicketHelper; import alfio.controller.api.v2.model.ReservationInfo; import alfio.controller.api.v2.model.ReservationInfo.TicketsByTicketCategory; import alfio.controller.api.v2.model.ReservationPaymentResult; @@ -29,8 +30,10 @@ import alfio.manager.*; import alfio.manager.support.PaymentResult; import alfio.manager.system.ConfigurationManager; +import alfio.manager.system.ReservationPriceCalculator; import alfio.model.*; import alfio.model.system.Configuration; +import alfio.model.system.ConfigurationKeys; import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.PaymentProxy; import alfio.model.transaction.PaymentToken; @@ -43,6 +46,7 @@ import alfio.util.TemplateResource; import lombok.AllArgsConstructor; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; @@ -52,9 +56,10 @@ import org.springframework.ui.Model; import org.springframework.util.MultiValueMap; import org.springframework.validation.BindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springframework.web.servlet.support.RequestContextUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -67,7 +72,12 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static alfio.model.PriceContainer.VatStatus.*; +import static alfio.model.PriceContainer.VatStatus.INCLUDED_EXEMPT; +import static alfio.model.system.Configuration.getSystemConfiguration; import static alfio.model.system.ConfigurationKeys.ALLOW_FREE_TICKETS_CANCELLATION; +import static alfio.model.system.ConfigurationKeys.ENABLE_ITALY_E_INVOICING; +import static alfio.util.MonetaryUtil.unitToCents; @RestController @AllArgsConstructor @@ -86,10 +96,10 @@ public class ReservationApiV2Controller { private final FileUploadManager fileUploadManager; private final TemplateManager templateManager; private final ExtensionManager extensionManager; + private final TicketHelper ticketHelper; + private final EuVatChecker vatChecker; /** - * See {@link ReservationController#showBookingPage(String, String, Model, Locale)} - * * Note: now it will return for any states of the reservation. * * @param eventName @@ -222,8 +232,8 @@ public ResponseEntity cancelPendingReservation(@PathVariable("eventName @PathVariable("reservationId") String reservationId, HttpServletRequest request) { - //FIXME check precondition (see ReservationController.redirectIfNotValid) - ticketReservationManager.cancelPendingReservation(reservationId, false, null); + getReservationWithPendingStatus(eventName, reservationId) + .ifPresent(er -> ticketReservationManager.cancelPendingReservation(reservationId, false, null)); SessionUtil.cleanupSession(request); return ResponseEntity.ok(true); } @@ -232,9 +242,10 @@ public ResponseEntity cancelPendingReservation(@PathVariable("eventName public ResponseEntity backToBook(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId) { - //FIXME check precondition (see ReservationController.redirectIfNotValid) + getReservationWithPendingStatus(eventName, reservationId) + .ifPresent(er -> ticketReservationRepository.updateValidationStatus(reservationId, false)); + - ticketReservationRepository.updateValidationStatus(reservationId, false); return ResponseEntity.ok(true); } @@ -273,14 +284,144 @@ public ResponseEntity> validateToOverview(@PathVariab @RequestParam("lang") String lang, @RequestBody ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, - HttpServletRequest request, - RedirectAttributes redirectAttributes) { + HttpServletRequest request) { - //FIXME check precondition (see ReservationController.redirectIfNotValid) - reservationController.validateToOverview(eventName, reservationId, contactAndTicketsForm, bindingResult, request, redirectAttributes, Locale.forLanguageTag(lang)); - var body = ValidatedResponse.toResponse(bindingResult, !bindingResult.hasErrors()); - return ResponseEntity.status(bindingResult.hasErrors() ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK).body(body); + return getReservationWithPendingStatus(eventName, reservationId).map(er -> { + var event = er.getLeft(); + var reservation = er.getRight(); + var locale = Locale.forLanguageTag(lang); + final TotalPrice reservationCost = ticketReservationManager.totalReservationCostWithVAT(reservation.withVatStatus(event.getVatStatus())); + Configuration.ConfigurationPathKey forceAssignmentKey = Configuration.from(event, ConfigurationKeys.FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION); + boolean forceAssignment = configurationManager.getBooleanConfigValue(forceAssignmentKey, false); + + if(forceAssignment || ticketReservationManager.containsCategoriesLinkedToGroups(reservationId, event.getId())) { + contactAndTicketsForm.setPostponeAssignment(false); + } + + boolean invoiceOnly = configurationManager.isInvoiceOnly(event); + + if(invoiceOnly && reservationCost.getPriceWithVAT() > 0) { + //override, that's why we save it + contactAndTicketsForm.setInvoiceRequested(true); + } + + CustomerName customerName = new CustomerName(contactAndTicketsForm.getFullName(), contactAndTicketsForm.getFirstName(), contactAndTicketsForm.getLastName(), event.mustUseFirstAndLastName(), false); + + + ticketReservationRepository.resetVat(reservationId, contactAndTicketsForm.isInvoiceRequested(), event.getVatStatus(), + reservation.getSrcPriceCts(), reservationCost.getPriceWithVAT(), reservationCost.getVAT(), Math.abs(reservationCost.getDiscount()), reservation.getCurrencyCode()); + if(contactAndTicketsForm.isBusiness()) { + checkAndApplyVATRules(eventName, reservationId, contactAndTicketsForm, bindingResult, event); + } + + //persist data + ticketReservationManager.updateReservation(reservationId, customerName, contactAndTicketsForm.getEmail(), + contactAndTicketsForm.getBillingAddressCompany(), contactAndTicketsForm.getBillingAddressLine1(), contactAndTicketsForm.getBillingAddressLine2(), + contactAndTicketsForm.getBillingAddressZip(), contactAndTicketsForm.getBillingAddressCity(), contactAndTicketsForm.getVatCountryCode(), + contactAndTicketsForm.getCustomerReference(), contactAndTicketsForm.getVatNr(), contactAndTicketsForm.isInvoiceRequested(), + contactAndTicketsForm.getAddCompanyBillingDetails(), contactAndTicketsForm.canSkipVatNrCheck(), false, locale); + + boolean italyEInvoicing = configurationManager.getBooleanConfigValue(Configuration.from(event, ENABLE_ITALY_E_INVOICING), false); + + if(italyEInvoicing) { + ticketReservationManager.updateReservationInvoicingAdditionalInformation(reservationId, + new TicketReservationInvoicingAdditionalInfo(getItalianInvoicingInfo(contactAndTicketsForm)) + ); + } + + assignTickets(event.getShortName(), reservationId, contactAndTicketsForm, bindingResult, request, true, true); + // + + Map formValidationParameters = Collections.singletonMap(ENABLE_ITALY_E_INVOICING, italyEInvoicing); + // + contactAndTicketsForm.validate(bindingResult, event, + ticketFieldRepository.findAdditionalFieldsForEvent(event.getId()), + new SameCountryValidator(configurationManager, extensionManager, event.getOrganizationId(), event.getId(), reservationId, vatChecker), + formValidationParameters); + // + + if(!bindingResult.hasErrors()) { + extensionManager.handleReservationValidation(event, reservation, contactAndTicketsForm, bindingResult); + } + + if(!bindingResult.hasErrors()) { + ticketReservationRepository.updateValidationStatus(reservationId, true); + } + + var body = ValidatedResponse.toResponse(bindingResult, !bindingResult.hasErrors()); + return ResponseEntity.status(bindingResult.hasErrors() ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK).body(body); + }).orElseGet(() -> ResponseEntity.notFound().build()); + } + + private TicketReservationInvoicingAdditionalInfo.ItalianEInvoicing getItalianInvoicingInfo(ContactAndTicketsForm contactAndTicketsForm) { + if("IT".equalsIgnoreCase(contactAndTicketsForm.getVatCountryCode())) { + return new TicketReservationInvoicingAdditionalInfo.ItalianEInvoicing(contactAndTicketsForm.getItalyEInvoicingFiscalCode(), + contactAndTicketsForm.getItalyEInvoicingReferenceType(), + contactAndTicketsForm.getItalyEInvoicingReferenceAddresseeCode(), + contactAndTicketsForm.getItalyEInvoicingReferencePEC()); + } + return null; + } + + private void assignTickets(String eventName, String reservationId, ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, HttpServletRequest request, boolean preAssign, boolean skipValidation) { + if(!contactAndTicketsForm.isPostponeAssignment()) { + contactAndTicketsForm.getTickets().forEach((ticketId, owner) -> { + if (preAssign) { + Optional bindingResultOptional = skipValidation ? Optional.empty() : Optional.of(bindingResult); + ticketHelper.preAssignTicket(eventName, reservationId, ticketId, owner, bindingResultOptional, request, (tr) -> { + }, Optional.empty()); + } else { + ticketHelper.assignTicket(eventName, ticketId, owner, Optional.of(bindingResult), request, (tr) -> { + }, Optional.empty(), true); + } + }); + } + } + + private void checkAndApplyVATRules(String eventName, String reservationId, ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, Event event) { + // VAT handling + String country = contactAndTicketsForm.getVatCountryCode(); + + // validate VAT presence if EU mode is enabled + if(vatChecker.isReverseChargeEnabledFor(event.getOrganizationId()) && isEUCountry(country)) { + ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "vatNr", "error.emptyField"); + } + + try { + Optional vatDetail = eventRepository.findOptionalByShortName(eventName) + .flatMap(e -> ticketReservationRepository.findOptionalReservationById(reservationId).map(r -> Pair.of(e, r))) + .filter(e -> EnumSet.of(INCLUDED, NOT_INCLUDED).contains(e.getKey().getVatStatus())) + .filter(e -> vatChecker.isReverseChargeEnabledFor(e.getKey().getOrganizationId())) + .flatMap(e -> vatChecker.checkVat(contactAndTicketsForm.getVatNr(), country, e.getKey())); + + + vatDetail.ifPresent(vatValidation -> { + if (!vatValidation.isValid()) { + bindingResult.rejectValue("vatNr", "error.vat"); + } else { + var reservation = ticketReservationManager.findById(reservationId).orElseThrow(); + PriceContainer.VatStatus vatStatus = determineVatStatus(event.getVatStatus(), vatValidation.isVatExempt()); + var updatedPrice = ticketReservationManager.totalReservationCostWithVAT(reservation.withVatStatus(vatStatus));// update VatStatus to the new value for calculating the new price + var calculator = new ReservationPriceCalculator(reservation.withVatStatus(vatStatus), updatedPrice, ticketReservationManager.findTicketsInReservation(reservationId), event); + ticketReservationRepository.updateBillingData(vatStatus, reservation.getSrcPriceCts(), + unitToCents(calculator.getFinalPrice()), unitToCents(calculator.getVAT()), unitToCents(calculator.getAppliedDiscount()), + reservation.getCurrencyCode(), StringUtils.trimToNull(vatValidation.getVatNr()), + country, contactAndTicketsForm.isInvoiceRequested(), reservationId); + vatChecker.logSuccessfulValidation(vatValidation, reservationId, event.getId()); + } + }); + } catch (IllegalStateException ise) {//vat checker failure + bindingResult.rejectValue("vatNr", "error.vatVIESDown"); + } + } + + + private Optional> getReservationWithPendingStatus(String eventName, String reservationId) { + return eventRepository.findOptionalByShortName(eventName) + .flatMap(event -> ticketReservationManager.findById(reservationId) + .filter(reservation -> reservation.getStatus() == TicketReservation.TicketReservationStatus.PENDING) + .flatMap(reservation -> Optional.of(Pair.of(event, reservation)))); } @PostMapping("/event/{eventName}/reservation/{reservationId}/re-send-email") @@ -288,10 +429,11 @@ public ResponseEntity reSendReservationConfirmationEmail(@PathVariable( @PathVariable("reservationId") String reservationId, @RequestParam("lang") String lang) { + + var res = eventRepository.findOptionalByShortName(eventName).map(event -> ticketReservationManager.findById(reservationId).map(ticketReservation -> { - Locale locale = Locale.forLanguageTag(lang); - ticketReservationManager.sendConfirmationEmail(event, ticketReservation, locale); + ticketReservationManager.sendConfirmationEmail(event, ticketReservation, Locale.forLanguageTag(lang)); return true; }).orElse(false) ).orElse(false); @@ -463,4 +605,15 @@ private Map formatDateForLocales(Event event, ZonedDateTime date return res; } + private boolean isEUCountry(String countryCode) { + return configurationManager.getRequiredValue(getSystemConfiguration(ConfigurationKeys.EU_COUNTRIES_LIST)).contains(countryCode); + } + + private static PriceContainer.VatStatus determineVatStatus(PriceContainer.VatStatus current, boolean isVatExempt) { + if(!isVatExempt) { + return current; + } + return current == NOT_INCLUDED ? NOT_INCLUDED_EXEMPT : INCLUDED_EXEMPT; + } + } diff --git a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java index 0cd1a8174d..2c6a99edbd 100644 --- a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java @@ -487,7 +487,7 @@ public void reservationFlowTest() throws Exception { assertFalse(selectedTicket.getTicketFieldConfigurationAfterStandard().get(0).isRequired()); var contactForm = new ContactAndTicketsForm(); - var validationErrorsRes = reservationApiV2Controller.validateToOverview(event.getShortName(), reservationId, "en", contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), new MockHttpServletRequest(), new RedirectAttributesModelMap()); + var validationErrorsRes = reservationApiV2Controller.validateToOverview(event.getShortName(), reservationId, "en", contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), new MockHttpServletRequest()); assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, validationErrorsRes.getStatusCode()); assertFalse(validationErrorsRes.getBody().isSuccess()); assertEquals(4, validationErrorsRes.getBody().getErrorCount()); // first name, last name, email + MISSING_ATTENDEE DATA @@ -506,13 +506,13 @@ public void reservationFlowTest() throws Exception { ticketForm.setEmail("tickettest@test.com"); contactForm.setTickets(Collections.singletonMap(reservation.getTicketsByCategory().get(0).getTickets().get(0).getUuid(), ticketForm)); - var overviewResFailed = reservationApiV2Controller.validateToOverview(event.getShortName(), reservationId, "en", contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), new MockHttpServletRequest(), new RedirectAttributesModelMap()); + var overviewResFailed = reservationApiV2Controller.validateToOverview(event.getShortName(), reservationId, "en", contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), new MockHttpServletRequest()); assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, overviewResFailed.getStatusCode()); checkStatus(reservationId, HttpStatus.OK, false, TicketReservation.TicketReservationStatus.PENDING); //add mandatory additional field ticketForm.setAdditional(Collections.singletonMap("field1", Collections.singletonList("value"))); - var overviewRes = reservationApiV2Controller.validateToOverview(event.getShortName(), reservationId, "en", contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), new MockHttpServletRequest(), new RedirectAttributesModelMap()); + var overviewRes = reservationApiV2Controller.validateToOverview(event.getShortName(), reservationId, "en", contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), new MockHttpServletRequest()); assertEquals(HttpStatus.OK, overviewRes.getStatusCode()); checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.PENDING); // @@ -521,7 +521,7 @@ public void reservationFlowTest() throws Exception { checkStatus(reservationId, HttpStatus.OK, false, TicketReservation.TicketReservationStatus.PENDING); - overviewRes = reservationApiV2Controller.validateToOverview(event.getShortName(), reservationId, "en", contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), new MockHttpServletRequest(), new RedirectAttributesModelMap()); + overviewRes = reservationApiV2Controller.validateToOverview(event.getShortName(), reservationId, "en", contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), new MockHttpServletRequest()); assertTrue(overviewRes.getBody().getValue()); checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.PENDING); From 551473693f4248932c8e36751db53c66b186b812 Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 13 Jun 2019 19:28:44 +0200 Subject: [PATCH 7/8] remove use of reservationController in reservation api v2 controller #588 --- .../v2/user/ReservationApiV2Controller.java | 101 ++++++++++++++---- .../user/ReservationFlowIntegrationTest.java | 4 +- 2 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java index 0acbd58df5..e8e91a3048 100644 --- a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java @@ -16,7 +16,6 @@ */ package alfio.controller.api.v2.user; -import alfio.controller.ReservationController; import alfio.controller.api.support.TicketHelper; import alfio.controller.api.v2.model.ReservationInfo; import alfio.controller.api.v2.model.ReservationInfo.TicketsByTicketCategory; @@ -28,38 +27,39 @@ import alfio.controller.support.SessionUtil; import alfio.controller.support.TemplateProcessor; import alfio.manager.*; +import alfio.manager.payment.PaymentSpecification; +import alfio.manager.payment.StripeCreditCardManager; import alfio.manager.support.PaymentResult; import alfio.manager.system.ConfigurationManager; import alfio.manager.system.ReservationPriceCalculator; import alfio.model.*; import alfio.model.system.Configuration; import alfio.model.system.ConfigurationKeys; -import alfio.model.transaction.PaymentMethod; -import alfio.model.transaction.PaymentProxy; -import alfio.model.transaction.PaymentToken; -import alfio.model.transaction.TransactionInitializationToken; +import alfio.model.transaction.*; import alfio.repository.EventRepository; import alfio.repository.TicketFieldRepository; import alfio.repository.TicketReservationRepository; +import alfio.util.ErrorsCode; import alfio.util.FileUtil; import alfio.util.TemplateManager; import alfio.util.TemplateResource; import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import org.springframework.ui.Model; import org.springframework.util.MultiValueMap; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -82,11 +82,11 @@ @RestController @AllArgsConstructor @RequestMapping("/api/v2/public/") +@Log4j2 public class ReservationApiV2Controller { private final EventManager eventManager; private final EventRepository eventRepository; - private final ReservationController reservationController; private final TicketReservationManager ticketReservationManager; private final TicketReservationRepository ticketReservationRepository; private final TicketFieldRepository ticketFieldRepository; @@ -98,6 +98,7 @@ public class ReservationApiV2Controller { private final ExtensionManager extensionManager; private final TicketHelper ticketHelper; private final EuVatChecker vatChecker; + private final RecaptchaService recaptchaService; /** * Note: now it will return for any states of the reservation. @@ -255,25 +256,77 @@ public ResponseEntity> handleReserva @RequestParam("lang") String lang, @RequestBody PaymentForm paymentForm, BindingResult bindingResult, - Model model, HttpServletRequest request, - RedirectAttributes redirectAttributes, HttpSession session) { - //FIXME check precondition (see ReservationController.redirectIfNotValid) - reservationController.handleReservation(eventName, reservationId, paymentForm, - bindingResult, model, request, Locale.forLanguageTag(lang), redirectAttributes, - session); - - var modelMap = model.asMap(); - if (modelMap.containsKey("paymentResultStatus")) { - PaymentResult paymentResult = (PaymentResult) modelMap.get("paymentResultStatus"); - if (paymentResult.isRedirect() && !bindingResult.hasErrors()) { + + return getReservationWithPendingStatus(eventName, reservationId).map(er -> { + + var event = er.getLeft(); + var ticketReservation = er.getRight(); + var locale = Locale.forLanguageTag(lang); + + if (!ticketReservation.getValidity().after(new Date())) { + bindingResult.reject(ErrorsCode.STEP_2_ORDER_EXPIRED); + } + + + final TotalPrice reservationCost = ticketReservationManager.totalReservationCostWithVAT(reservationId); + + paymentForm.validate(bindingResult, event, reservationCost); + if (bindingResult.hasErrors()) { + return buildReservationPaymentStatus(bindingResult); + } + + if(isCaptchaInvalid(reservationCost.getPriceWithVAT(), paymentForm.getPaymentMethod(), request, event)) { + log.debug("captcha validation failed."); + bindingResult.reject(ErrorsCode.STEP_2_CAPTCHA_VALIDATION_FAILED); + } + + if(!bindingResult.hasErrors()) { + extensionManager.handleReservationValidation(event, ticketReservation, paymentForm, bindingResult); + } + + if (bindingResult.hasErrors()) { + return buildReservationPaymentStatus(bindingResult); + } + + CustomerName customerName = new CustomerName(ticketReservation.getFullName(), ticketReservation.getFirstName(), ticketReservation.getLastName(), event.mustUseFirstAndLastName()); + + OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event); + + PaymentToken paymentToken = (PaymentToken) session.getAttribute(PaymentManager.PAYMENT_TOKEN); + if(paymentToken == null && StringUtils.isNotEmpty(paymentForm.getGatewayToken())) { + paymentToken = paymentManager.buildPaymentToken(paymentForm.getGatewayToken(), paymentForm.getPaymentMethod(), new PaymentContext(event, reservationId)); + } + PaymentSpecification spec = new PaymentSpecification(reservationId, paymentToken, reservationCost.getPriceWithVAT(), + event, ticketReservation.getEmail(), customerName, ticketReservation.getBillingAddress(), ticketReservation.getCustomerReference(), + locale, ticketReservation.isInvoiceRequested(), !ticketReservation.isDirectAssignmentRequested(), + orderSummary, ticketReservation.getVatCountryCode(), ticketReservation.getVatNr(), ticketReservation.getVatStatus(), + Boolean.TRUE.equals(paymentForm.getTermAndConditionsAccepted()), Boolean.TRUE.equals(paymentForm.getPrivacyPolicyAccepted())); + + final PaymentResult status = ticketReservationManager.performPayment(spec, reservationCost, SessionUtil.retrieveSpecialPriceSessionId(request), + Optional.ofNullable(paymentForm.getPaymentMethod())); + + if(!status.isSuccessful()) { + String errorMessageCode = status.getErrorCode().orElse(StripeCreditCardManager.STRIPE_UNEXPECTED); + MessageSourceResolvable message = new DefaultMessageSourceResolvable(new String[]{errorMessageCode, StripeCreditCardManager.STRIPE_UNEXPECTED}); + bindingResult.reject(ErrorsCode.STEP_2_PAYMENT_PROCESSING_ERROR, new Object[]{messageSource.getMessage(message, locale)}, null); + //SessionUtil.addToFlash(bindingResult, redirectAttributes); + SessionUtil.removePaymentToken(request); + return buildReservationPaymentStatus(bindingResult); + } + + if (status.isRedirect() && !bindingResult.hasErrors()) { var body = ValidatedResponse.toResponse(bindingResult, - new ReservationPaymentResult(!bindingResult.hasErrors(), true, paymentResult.getRedirectUrl(), paymentResult.isFailed(), paymentResult.getGatewayIdOrNull())); + new ReservationPaymentResult(!bindingResult.hasErrors(), true, status.getRedirectUrl(), status.isFailed(), status.getGatewayIdOrNull())); return ResponseEntity.ok(body); + } else { + return buildReservationPaymentStatus(bindingResult); } - } + }).orElseGet(() -> ResponseEntity.notFound().build()); + } + private static ResponseEntity> buildReservationPaymentStatus(BindingResult bindingResult) { var body = ValidatedResponse.toResponse(bindingResult, new ReservationPaymentResult(!bindingResult.hasErrors(), false, null, true, null)); return ResponseEntity.status(bindingResult.hasErrors() ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK).body(body); } @@ -616,4 +669,10 @@ private static PriceContainer.VatStatus determineVatStatus(PriceContainer.VatSta return current == NOT_INCLUDED ? NOT_INCLUDED_EXEMPT : INCLUDED_EXEMPT; } + + private boolean isCaptchaInvalid(int cost, PaymentProxy paymentMethod, HttpServletRequest request, EventAndOrganizationId event) { + return (cost == 0 || paymentMethod == PaymentProxy.OFFLINE || paymentMethod == PaymentProxy.ON_SITE) + && configurationManager.isRecaptchaForOfflinePaymentEnabled(event) + && !recaptchaService.checkRecaptcha(null, request); + } } diff --git a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java index 2c6a99edbd..171ead440a 100644 --- a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java @@ -528,7 +528,7 @@ public void reservationFlowTest() throws Exception { var paymentForm = new PaymentForm(); var handleResError = reservationApiV2Controller.handleReservation(event.getShortName(), reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), - new BindingAwareModelMap(), new MockHttpServletRequest(), new RedirectAttributesModelMap(), new MockHttpSession()); + new MockHttpServletRequest(), new MockHttpSession()); assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, handleResError.getStatusCode()); @@ -536,7 +536,7 @@ public void reservationFlowTest() throws Exception { paymentForm.setTermAndConditionsAccepted(true); paymentForm.setPaymentMethod(PaymentProxy.OFFLINE); var handleRes = reservationApiV2Controller.handleReservation(event.getShortName(), reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), - new BindingAwareModelMap(), new MockHttpServletRequest(), new RedirectAttributesModelMap(), new MockHttpSession()); + new MockHttpServletRequest(), new MockHttpSession()); assertEquals(HttpStatus.OK, handleRes.getStatusCode()); checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT); From abe3cd9455ec77acbbbd604d3b8fd72f0501c9ed Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 13 Jun 2019 21:51:47 +0200 Subject: [PATCH 8/8] enable the new angular frontend, remove most of the unused stuff #588 --- build.gradle | 4 +- .../java/alfio/config/MvcConfiguration.java | 5 + .../DynamicResourcesController.java | 75 -- .../alfio/controller/EventController.java | 475 ----------- .../alfio/controller/IndexController.java | 105 +++ .../controller/InvoiceReceiptController.java | 119 --- .../controller/ReservationController.java | 764 ------------------ .../alfio/controller/TicketController.java | 249 ------ .../controller/WaitingQueueController.java | 60 -- .../api/ReservationApiController.java | 95 --- .../api/WaitingQueueApiController.java | 82 -- .../controller/decorator/DecoratorUtil.java | 43 - .../decorator/SaleableAdditionalService.java | 33 - .../decorator/SaleableTicketCategory.java | 13 - .../alfio/controller/support/SessionUtil.java | 12 - .../controller/support/TicketDecorator.java | 112 --- .../WEB-INF/templates/event/404-not-found.ms | 14 - .../event/500-internal-server-error.ms | 14 - .../templates/event/additional-field.ms | 43 - .../templates/event/additional-service.ms | 90 --- .../templates/event/assign-ticket-form.ms | 61 -- .../templates/event/assign-ticket-result.ms | 5 - .../templates/event/attendee-fields.ms | 77 -- .../WEB-INF/templates/event/event-details.ms | 37 - .../WEB-INF/templates/event/event-list.ms | 50 -- .../webapp/WEB-INF/templates/event/header.ms | 9 - .../WEB-INF/templates/event/invoice-fields.ms | 163 ---- .../templates/event/output-event-image-tag.ms | 8 - .../WEB-INF/templates/event/overview.ms | 225 ------ .../WEB-INF/templates/event/page-bottom.ms | 13 - .../WEB-INF/templates/event/page-top.ms | 44 - .../WEB-INF/templates/event/payment/mollie.ms | 1 - .../templates/event/payment/offline.ms | 12 - .../templates/event/payment/on-site.ms | 11 - .../WEB-INF/templates/event/payment/paypal.ms | 13 - .../WEB-INF/templates/event/payment/stripe.ms | 85 -- .../event/reservation-page-complete.ms | 116 --- .../event/reservation-page-error-status.ms | 11 - .../event/reservation-page-not-found.ms | 10 - .../templates/event/reservation-page.ms | 156 ---- .../event/reservation-processing-payment.ms | 79 -- .../event/reservation-waiting-for-payment.ms | 51 -- .../templates/event/session-expired.ms | 7 - .../WEB-INF/templates/event/show-event.ms | 223 ----- .../WEB-INF/templates/event/show-ticket.ms | 58 -- .../templates/event/ticket-category.ms | 66 -- .../WEB-INF/templates/event/update-ticket.ms | 47 -- .../waiting-queue-subscription-result.ms | 10 - src/main/webapp/resources/css/application.css | 296 ------- .../webapp/resources/css/payment/stripe.css | 30 - .../resources/js/countdownjs/countdown.min.js | 20 - .../resources/js/event/attendee-form.js | 34 - .../resources/js/event/bootstrap-handler.js | 6 - .../resources/js/event/jquery.shorten.js | 146 ---- .../resources/js/event/overview-page.js | 228 ------ .../js/event/reservation-page-complete.js | 154 ---- .../resources/js/event/reservation-page.js | 287 ------- .../reservation-processing-payment-page.js | 30 - .../webapp/resources/js/event/show-event.js | 93 --- .../webapp/resources/js/event/show-ticket.js | 11 - .../resources/js/event/update-ticket.js | 6 - .../resources/js/event/waiting-queue.js | 65 -- src/main/webapp/resources/js/h5f/h5f.min.js | 4 - .../js/payment/creditcard-embedded-stripe.js | 154 ---- .../resources/js/payment/creditcard-stripe.js | 65 -- .../resources/js/payment/free-of-charge.js | 30 - .../webapp/resources/js/payment/offline.js | 30 - .../webapp/resources/js/payment/on-site.js | 30 - .../webapp/resources/js/payment/paypal.js | 30 - .../ReservationFlowIntegrationTest.java | 626 -------------- .../api/v1/EventApiV1IntegrationTest.java | 2 +- .../user/ReservationFlowIntegrationTest.java | 162 +++- .../decorator/DecoratorUtilTest.java | 40 - 73 files changed, 270 insertions(+), 6364 deletions(-) delete mode 100644 src/main/java/alfio/controller/DynamicResourcesController.java delete mode 100644 src/main/java/alfio/controller/EventController.java create mode 100644 src/main/java/alfio/controller/IndexController.java delete mode 100644 src/main/java/alfio/controller/InvoiceReceiptController.java delete mode 100644 src/main/java/alfio/controller/ReservationController.java delete mode 100644 src/main/java/alfio/controller/TicketController.java delete mode 100644 src/main/java/alfio/controller/WaitingQueueController.java delete mode 100644 src/main/java/alfio/controller/api/ReservationApiController.java delete mode 100644 src/main/java/alfio/controller/api/WaitingQueueApiController.java delete mode 100644 src/main/java/alfio/controller/decorator/DecoratorUtil.java delete mode 100644 src/main/java/alfio/controller/support/TicketDecorator.java delete mode 100644 src/main/webapp/WEB-INF/templates/event/404-not-found.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/500-internal-server-error.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/additional-field.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/additional-service.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/assign-ticket-form.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/assign-ticket-result.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/attendee-fields.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/event-details.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/event-list.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/header.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/invoice-fields.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/output-event-image-tag.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/overview.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/page-bottom.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/page-top.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/payment/mollie.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/payment/offline.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/payment/on-site.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/payment/paypal.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/payment/stripe.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/reservation-page-complete.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/reservation-page-error-status.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/reservation-page-not-found.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/reservation-page.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/reservation-processing-payment.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/reservation-waiting-for-payment.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/session-expired.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/show-event.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/show-ticket.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/ticket-category.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/update-ticket.ms delete mode 100644 src/main/webapp/WEB-INF/templates/event/waiting-queue-subscription-result.ms delete mode 100644 src/main/webapp/resources/css/application.css delete mode 100644 src/main/webapp/resources/css/payment/stripe.css delete mode 100644 src/main/webapp/resources/js/countdownjs/countdown.min.js delete mode 100644 src/main/webapp/resources/js/event/attendee-form.js delete mode 100644 src/main/webapp/resources/js/event/bootstrap-handler.js delete mode 100644 src/main/webapp/resources/js/event/jquery.shorten.js delete mode 100644 src/main/webapp/resources/js/event/overview-page.js delete mode 100644 src/main/webapp/resources/js/event/reservation-page-complete.js delete mode 100644 src/main/webapp/resources/js/event/reservation-page.js delete mode 100644 src/main/webapp/resources/js/event/reservation-processing-payment-page.js delete mode 100644 src/main/webapp/resources/js/event/show-event.js delete mode 100644 src/main/webapp/resources/js/event/show-ticket.js delete mode 100644 src/main/webapp/resources/js/event/update-ticket.js delete mode 100644 src/main/webapp/resources/js/event/waiting-queue.js delete mode 100644 src/main/webapp/resources/js/h5f/h5f.min.js delete mode 100644 src/main/webapp/resources/js/payment/creditcard-embedded-stripe.js delete mode 100644 src/main/webapp/resources/js/payment/creditcard-stripe.js delete mode 100644 src/main/webapp/resources/js/payment/free-of-charge.js delete mode 100644 src/main/webapp/resources/js/payment/offline.js delete mode 100644 src/main/webapp/resources/js/payment/on-site.js delete mode 100644 src/main/webapp/resources/js/payment/paypal.js delete mode 100644 src/test/java/alfio/controller/ReservationFlowIntegrationTest.java delete mode 100644 src/test/java/alfio/controller/decorator/DecoratorUtilTest.java diff --git a/build.gradle b/build.gradle index ebd420bbd0..0e9e2eff10 100644 --- a/build.gradle +++ b/build.gradle @@ -47,6 +47,7 @@ apply plugin: 'java' apply plugin: 'war' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' +apply plugin: 'project-report' //as pointed out by @facundofarias, we should validate minimum javac version @@ -150,7 +151,8 @@ dependencies { compile 'ch.digitalfondue.basicxlsx:basicxlsx:0.5.0' compile 'org.imgscalr:imgscalr-lib:4.2' compile 'org.aspectj:aspectjweaver:1.9.4' - //compile 'com.github.alfio-event:alf.io-public-frontend:-SNAPSHOT' + + compile 'com.github.alfio-event:alf.io-public-frontend:8fd67bc9f6' testCompile 'com.opentable.components:otj-pg-embedded:0.13.1' diff --git a/src/main/java/alfio/config/MvcConfiguration.java b/src/main/java/alfio/config/MvcConfiguration.java index a597f5fbc3..214fe1fa2d 100644 --- a/src/main/java/alfio/config/MvcConfiguration.java +++ b/src/main/java/alfio/config/MvcConfiguration.java @@ -108,6 +108,11 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { ResourceHandlerRegistration reg = registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); int cacheMinutes = environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_LIVE)) ? 15 : 0; reg.setCachePeriod(cacheMinutes * 60); + + // + registry + .addResourceHandler("/webjars/**") + .addResourceLocations("/webjars/"); } @Override diff --git a/src/main/java/alfio/controller/DynamicResourcesController.java b/src/main/java/alfio/controller/DynamicResourcesController.java deleted file mode 100644 index ba5f5f50d3..0000000000 --- a/src/main/java/alfio/controller/DynamicResourcesController.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller; - -import alfio.manager.system.ConfigurationManager; -import alfio.model.Event; -import alfio.model.system.Configuration; -import alfio.model.system.Configuration.ConfigurationPathKey; -import alfio.repository.EventRepository; -import alfio.util.TemplateManager; -import alfio.util.TemplateResource; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; - -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.util.*; - -import static alfio.model.system.ConfigurationKeys.GOOGLE_ANALYTICS_ANONYMOUS_MODE; -import static alfio.model.system.ConfigurationKeys.GOOGLE_ANALYTICS_KEY; - -@Controller -public class DynamicResourcesController { - - private static final String GOOGLE_ANALYTICS_SCRIPT = "var _gaq = _gaq || [];_gaq.push(['_setAccount', '%s']);_gaq.push(['_trackPageview']);"; - private static final String EMPTY = "(function(){})();"; - private final ConfigurationManager configurationManager; - private final TemplateManager templateManager; - private final EventRepository eventRepository; - - @Autowired - public DynamicResourcesController(ConfigurationManager configurationManager, TemplateManager templateManager, EventRepository eventRepository) { - this.configurationManager = configurationManager; - this.templateManager = templateManager; - this.eventRepository = eventRepository; - } - - @RequestMapping("/resources/js/google-analytics") - public void getGoogleAnalyticsScript(HttpSession session, HttpServletResponse response, @RequestParam("e") Integer eventId) throws IOException { - response.setContentType("application/javascript"); - Optional ev = Optional.ofNullable(eventId).flatMap(id -> eventRepository.findOptionalById(id)); - ConfigurationPathKey pathKey = ev.map(e -> Configuration.from(e, GOOGLE_ANALYTICS_KEY)).orElseGet(() -> Configuration.getSystemConfiguration(GOOGLE_ANALYTICS_KEY)); - final Optional id = configurationManager.getStringConfigValue(pathKey); - final String script; - ConfigurationPathKey anonymousPathKey = ev.map(e -> Configuration.from(e, GOOGLE_ANALYTICS_ANONYMOUS_MODE)).orElseGet(() -> Configuration.getSystemConfiguration(GOOGLE_ANALYTICS_ANONYMOUS_MODE)); - if(id.isPresent() && configurationManager.getBooleanConfigValue(anonymousPathKey, true)) { - String trackingId = Optional.ofNullable(StringUtils.trimToNull((String)session.getAttribute("GA_TRACKING_ID"))).orElseGet(() -> UUID.randomUUID().toString()); - Map model = new HashMap<>(); - model.put("clientId", trackingId); - model.put("account", id.get()); - script = templateManager.renderTemplate(ev, TemplateResource.GOOGLE_ANALYTICS, model, Locale.ENGLISH); - } else { - script = id.map(x -> String.format(GOOGLE_ANALYTICS_SCRIPT, x)).orElse(EMPTY); - } - response.getWriter().write(script); - } -} diff --git a/src/main/java/alfio/controller/EventController.java b/src/main/java/alfio/controller/EventController.java deleted file mode 100644 index 3eaaf31124..0000000000 --- a/src/main/java/alfio/controller/EventController.java +++ /dev/null @@ -1,475 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller; - - -import alfio.controller.decorator.EventDescriptor; -import alfio.controller.decorator.SaleableAdditionalService; -import alfio.controller.decorator.SaleableTicketCategory; -import alfio.controller.form.ReservationForm; -import alfio.controller.support.SessionUtil; -import alfio.manager.EventManager; -import alfio.manager.EventStatisticsManager; -import alfio.manager.RecaptchaService; -import alfio.manager.TicketReservationManager; -import alfio.manager.i18n.I18nManager; -import alfio.manager.system.ConfigurationManager; -import alfio.model.*; -import alfio.model.modification.TicketReservationModification; -import alfio.model.modification.support.LocationDescriptor; -import alfio.model.result.ValidationResult; -import alfio.model.system.Configuration; -import alfio.model.system.ConfigurationKeys; -import alfio.repository.*; -import alfio.repository.user.OrganizationRepository; -import alfio.util.ErrorsCode; -import alfio.util.EventUtil; -import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.time.DateUtils; -import org.springframework.context.MessageSource; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.validation.BeanPropertyBindingResult; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.context.request.ServletWebRequest; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.*; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import static alfio.controller.support.SessionUtil.addToFlash; -import static alfio.model.PromoCodeDiscount.categoriesOrNull; -import static alfio.model.system.Configuration.getSystemConfiguration; -import static alfio.model.system.ConfigurationKeys.RECAPTCHA_API_KEY; - -@Controller -@AllArgsConstructor -public class EventController { - - private static final String REDIRECT = "redirect:"; - private final EventRepository eventRepository; - private final EventDescriptionRepository eventDescriptionRepository; - private final I18nManager i18nManager; - private final TicketCategoryRepository ticketCategoryRepository; - private final TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository; - private final ConfigurationManager configurationManager; - private final OrganizationRepository organizationRepository; - private final SpecialPriceRepository specialPriceRepository; - private final PromoCodeDiscountRepository promoCodeRepository; - private final EventManager eventManager; - private final TicketReservationManager ticketReservationManager; - private final EventStatisticsManager eventStatisticsManager; - private final AdditionalServiceRepository additionalServiceRepository; - private final AdditionalServiceTextRepository additionalServiceTextRepository; - private final TicketRepository ticketRepository; - private final RecaptchaService recaptchaService; - private final MessageSource messageSource; - - - @RequestMapping(value = "/", method = RequestMethod.HEAD) - public ResponseEntity replyToProxy() { - return ResponseEntity.ok("Up and running!"); - } - - @RequestMapping(value = "/healthz", method = RequestMethod.GET) - public ResponseEntity replyToK8s() { - return ResponseEntity.ok("Up and running!"); - } - - @RequestMapping(value = {"/"}, method = RequestMethod.GET) - public String listEvents(Model model, Locale locale) { - List events = eventManager.getPublishedEvents(); - if(events.size() == 1) { - return REDIRECT + "/event/" + events.get(0).getShortName() + "/"; - } else { - model.addAttribute("events", events.stream().map(e -> { - String eventDescription = eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(e.getId(), EventDescription.EventDescriptionType.DESCRIPTION, locale.getLanguage()).orElse(""); - return new EventDescriptor(e, eventDescription); - }).collect(Collectors.toList())); - model.addAttribute("pageTitle", "event-list.header.title"); - model.addAttribute("event", null); - - model.addAttribute("showAvailableLanguagesInPageTop", true); - model.addAttribute("availableLanguages", i18nManager.getSupportedLanguages()); - return "/event/event-list"; - } - } - - - @RequestMapping("/session-expired") - public String sessionExpired(Model model) { - model.addAttribute("pageTitle", "session-expired.header.title"); - model.addAttribute("event", null); - return "/event/session-expired"; - } - - @RequestMapping(value = "/event/{eventName}/promoCode/{promoCode}", method = RequestMethod.POST) - @ResponseBody - public ValidationResult savePromoCode(@PathVariable("eventName") String eventName, - @PathVariable("promoCode") String promoCode, - Model model, - HttpServletRequest request) { - - SessionUtil.cleanupSession(request); - - Optional optional = eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName); - if(optional.isEmpty()) { - return ValidationResult.failed(new ValidationResult.ErrorDescriptor("event", "")); - } - EventAndOrganizationId event = optional.get(); - ZoneId eventZoneId = eventRepository.getZoneIdByEventId(event.getId()); - ZonedDateTime now = ZonedDateTime.now(eventZoneId); - Optional maybeSpecialCode = Optional.ofNullable(StringUtils.trimToNull(promoCode)); - Optional specialCode = maybeSpecialCode.flatMap(specialPriceRepository::getByCode); - Optional promotionCodeDiscount = maybeSpecialCode.flatMap((trimmedCode) -> promoCodeRepository.findPromoCodeInEventOrOrganization(event.getId(), trimmedCode)); - - if(specialCode.isPresent()) { - if (eventManager.getOptionalByIdAndActive(specialCode.get().getTicketCategoryId(), event.getId()).isEmpty()) { - return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", "")); - } - - if (specialCode.get().getStatus() != SpecialPrice.Status.FREE) { - return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", "")); - } - - } else if (promotionCodeDiscount.isPresent() && !promotionCodeDiscount.get().isCurrentlyValid(eventZoneId, now)) { - return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", "")); - } else if (promotionCodeDiscount.isPresent() && isDiscountCodeUsageExceeded(promotionCodeDiscount.get())){ - return ValidationResult.failed(new ValidationResult.ErrorDescriptor("usage", "")); - } else if(promotionCodeDiscount.isEmpty()) { - return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", "")); - } - - if(!model.asMap().containsKey("hasErrors")) { - if(specialCode.isPresent()) { - SessionUtil.saveSpecialPriceCode(maybeSpecialCode.get(), request); - } else { - SessionUtil.savePromotionCodeDiscount(maybeSpecialCode.get(), request); - } - return ValidationResult.success(); - } - return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", "")); - } - - private boolean isDiscountCodeUsageExceeded(PromoCodeDiscount discount) { - return discount.getMaxUsage() != null && discount.getMaxUsage() <= promoCodeRepository.countConfirmedPromoCode(discount.getId(), categoriesOrNull(discount), null, categoriesOrNull(discount) != null ? "X" : null); - } - - @RequestMapping(value = "/event/{eventName}", method = {RequestMethod.GET, RequestMethod.HEAD}) - public String showEvent(@PathVariable("eventName") String eventName, - Model model, HttpServletRequest request, Locale locale) { - - return eventRepository.findOptionalByShortName(eventName).filter(e -> e.getStatus() != Event.Status.DISABLED).map(event -> { - Optional maybeSpecialCode = SessionUtil.retrieveSpecialPriceCode(request); - Optional specialCode = maybeSpecialCode.flatMap(specialPriceRepository::getByCode); - - Optional promoCodeDiscount = SessionUtil.retrievePromotionCodeDiscount(request) - .flatMap((code) -> promoCodeRepository.findPromoCodeInEventOrOrganization(event.getId(), code)); - - final ZonedDateTime now = ZonedDateTime.now(event.getZoneId()); - //hide access restricted ticket categories - List ticketCategories = ticketCategoryRepository.findAllTicketCategories(event.getId()); - Map categoriesDescription = ticketCategoryDescriptionRepository.descriptionsByTicketCategory(ticketCategories.stream().map(TicketCategory::getId).collect(Collectors.toList()), locale.getLanguage()); - - List saleableTicketCategories = ticketCategories.stream() - .filter((c) -> !c.isAccessRestricted() || shouldDisplayRestrictedCategory(specialCode, c, promoCodeDiscount)) - .map((m) -> { - int maxTickets = configurationManager.getIntConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), m.getId(), ConfigurationKeys.MAX_AMOUNT_OF_TICKETS_BY_RESERVATION), 5); - PromoCodeDiscount filteredPromoCode = promoCodeDiscount.filter(promoCode -> shouldApplyDiscount(promoCode, m)).orElse(null); - if(specialCode.isPresent()) { - maxTickets = Math.min(1, maxTickets); - } else if(filteredPromoCode != null && filteredPromoCode.getMaxUsage() != null) { - maxTickets = filteredPromoCode.getMaxUsage() - promoCodeRepository.countConfirmedPromoCode(filteredPromoCode.getId(), categoriesOrNull(filteredPromoCode), null, categoriesOrNull(filteredPromoCode) != null ? "X" : null); - } - return new SaleableTicketCategory(m, categoriesDescription.getOrDefault(m.getId(), ""), - now, event, ticketReservationManager.countAvailableTickets(event, m), maxTickets, - filteredPromoCode); - }) - .collect(Collectors.toList()); - // - - Map> geoInfoConfiguration = configurationManager.getStringConfigValueFrom( - Configuration.from(event, ConfigurationKeys.MAPS_PROVIDER), - Configuration.from(event, ConfigurationKeys.MAPS_CLIENT_API_KEY), - Configuration.from(event, ConfigurationKeys.MAPS_HERE_APP_ID), - Configuration.from(event, ConfigurationKeys.MAPS_HERE_APP_CODE)); - - LocationDescriptor ld = LocationDescriptor.fromGeoData(event.getLatLong(), TimeZone.getTimeZone(event.getTimeZone()), geoInfoConfiguration); - - final boolean hasAccessPromotions = configurationManager.getBooleanConfigValue(Configuration.from(event, ConfigurationKeys.DISPLAY_DISCOUNT_CODE_BOX), true) && - (ticketCategoryRepository.countAccessRestrictedRepositoryByEventId(event.getId()) > 0 || - promoCodeRepository.countByEventAndOrganizationId(event.getId(), event.getOrganizationId()) > 0); - - String eventDescription = eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(event.getId(), EventDescription.EventDescriptionType.DESCRIPTION, locale.getLanguage()).orElse(""); - - final EventDescriptor eventDescriptor = new EventDescriptor(event, eventDescription); - List expiredCategories = saleableTicketCategories.stream().filter(SaleableTicketCategory::getExpired).collect(Collectors.toList()); - List validCategories = saleableTicketCategories.stream().filter(tc -> !tc.getExpired()).collect(Collectors.toList()); - List additionalServices = additionalServiceRepository.loadAllForEvent(event.getId()).stream().map((as) -> getSaleableAdditionalService(event, locale, as, promoCodeDiscount.orElse(null))).collect(Collectors.toList()); - Predicate waitingQueueTargetCategory = tc -> !tc.getExpired() && !tc.isBounded(); - - List notExpiredServices = additionalServices.stream().filter(SaleableAdditionalService::isNotExpired).collect(Collectors.toList()); - - List supplements = adjustIndex(0, notExpiredServices.stream().filter(a -> a.getType() == AdditionalService.AdditionalServiceType.SUPPLEMENT).collect(Collectors.toList())); - List donations = adjustIndex(supplements.size(), notExpiredServices.stream().filter(a -> a.getType() == AdditionalService.AdditionalServiceType.DONATION).collect(Collectors.toList())); - - boolean usePartnerCode = configurationManager.getBooleanConfigValue(Configuration.from(event, ConfigurationKeys.USE_PARTNER_CODE_INSTEAD_OF_PROMOTIONAL), false); - model.addAttribute("event", eventDescriptor)// - .addAttribute("organization", organizationRepository.getById(event.getOrganizationId())) - .addAttribute("ticketCategories", validCategories)// - .addAttribute("expiredCategories", expiredCategories)// - .addAttribute("containsExpiredCategories", configurationManager.getBooleanConfigValue(Configuration.from(event, ConfigurationKeys.DISPLAY_EXPIRED_CATEGORIES), true) && !expiredCategories.isEmpty())// - .addAttribute("showNoCategoriesWarning", validCategories.isEmpty()) - .addAttribute("hasAccessPromotions", hasAccessPromotions) - .addAttribute("promoCode", specialCode.map(SpecialPrice::getCode).orElse(null)) - .addAttribute("locationDescriptor", ld) - .addAttribute("pageTitle", "show-event.header.title") - .addAttribute("hasPromoCodeDiscount", promoCodeDiscount.filter(c -> c.getCodeType() == PromoCodeDiscount.CodeType.DISCOUNT).isPresent()) - .addAttribute("hasAccessCode", promoCodeDiscount.filter(c -> c.getCodeType() == PromoCodeDiscount.CodeType.ACCESS).isPresent()) - .addAttribute("promoCodeDiscount", promoCodeDiscount.orElse(null)) - .addAttribute("displayWaitingQueueForm", EventUtil.displayWaitingQueueForm(event, saleableTicketCategories, configurationManager, eventStatisticsManager.noSeatsAvailable())) - .addAttribute("displayCategorySelectionForWaitingQueue", saleableTicketCategories.stream().filter(waitingQueueTargetCategory).count() > 1) - .addAttribute("unboundedCategories", saleableTicketCategories.stream().filter(waitingQueueTargetCategory).collect(Collectors.toList())) - .addAttribute("preSales", EventUtil.isPreSales(event, saleableTicketCategories)) - .addAttribute("userLanguage", locale.getLanguage()) - .addAttribute("showAdditionalServices", !notExpiredServices.isEmpty()) - .addAttribute("showAdditionalServicesDonations", !donations.isEmpty()) - .addAttribute("showAdditionalServicesSupplements", !supplements.isEmpty()) - .addAttribute("enabledAdditionalServicesDonations", donations) - .addAttribute("enabledAdditionalServicesSupplements", supplements) - .addAttribute("forwardButtonDisabled", saleableTicketCategories.stream().noneMatch(SaleableTicketCategory::getSaleable)) - .addAttribute("useFirstAndLastName", event.mustUseFirstAndLastName()) - .addAttribute("validityStart", event.getBegin()) - .addAttribute("validityEnd", event.getEnd()) - .addAttribute("usePartnerCode", usePartnerCode) - .addAttribute("promoCodeDescription", messageSource.getMessage("show-event.promo-code-type."+(usePartnerCode ? "partner" : "promotional"), null, null, locale)); - - if(configurationManager.isRecaptchaForTicketSelectionEnabled(event)) { - model.addAttribute("captchaForTicketSelectionEnabled", true) - .addAttribute("recaptchaApiKey", configurationManager.getStringConfigValue(getSystemConfiguration(RECAPTCHA_API_KEY), null)); - } - - model.asMap().putIfAbsent("hasErrors", false);// - return "/event/show-event"; - }).orElse(REDIRECT + "/"); - } - - private boolean shouldDisplayRestrictedCategory(Optional specialCode, TicketCategory c, Optional optionalPromoCode) { - if(optionalPromoCode.isPresent()) { - var promoCode = optionalPromoCode.get(); - if(promoCode.getCodeType() == PromoCodeDiscount.CodeType.ACCESS && c.getId() == promoCode.getHiddenCategoryId()) { - return true; - } - } - return specialCode.filter(sc -> sc.getTicketCategoryId() == c.getId()).isPresent(); - } - - - enum CodeType { - SPECIAL_PRICE, PROMO_CODE_DISCOUNT, TICKET_CATEGORY_CODE, NOT_FOUND - } - - //not happy with that code... - private CodeType getCodeType(int eventId, String code) { - String trimmedCode = StringUtils.trimToNull(code); - if(trimmedCode == null) { - return CodeType.NOT_FOUND; - } else if(specialPriceRepository.getByCode(trimmedCode).isPresent()) { - return CodeType.SPECIAL_PRICE; - } else if (promoCodeRepository.findPromoCodeInEventOrOrganization(eventId, trimmedCode).isPresent()) { - return CodeType.PROMO_CODE_DISCOUNT; - } else if (ticketCategoryRepository.findCodeInEvent(eventId, trimmedCode).isPresent()) { - return CodeType.TICKET_CATEGORY_CODE; - } else { - return CodeType.NOT_FOUND; - } - } - - @RequestMapping(value = "/event/{eventName}/code/{code}", method = {RequestMethod.GET, RequestMethod.HEAD}) - public String handleCode(@PathVariable("eventName") String eventName, @PathVariable("code") String code, Model model, ServletWebRequest request, RedirectAttributes redirectAttributes, Locale locale) { - String trimmedCode = StringUtils.trimToNull(code); - return eventRepository.findOptionalByShortName(eventName).map(event -> { - String redirectToEvent = "redirect:/event/" + eventName + "/"; - ValidationResult res = savePromoCode(eventName, trimmedCode, model, request.getRequest()); - CodeType codeType = getCodeType(event.getId(), trimmedCode); - if(res.isSuccess() && codeType == CodeType.PROMO_CODE_DISCOUNT) { - return redirectToEvent; - } else if (codeType == CodeType.TICKET_CATEGORY_CODE) { - TicketCategory category = ticketCategoryRepository.findCodeInEvent(event.getId(), trimmedCode).orElseThrow(); - if(!category.isAccessRestricted()) { - return makeSimpleReservation(eventName, request, redirectAttributes, locale, null, event, category.getId()); - } else { - return initReservationForHiddenCategory(eventName, model, request, redirectAttributes, locale, event, redirectToEvent, category.getId()); - } - } else if (res.isSuccess() && codeType == CodeType.SPECIAL_PRICE) { - int ticketCategoryId = specialPriceRepository.getByCode(trimmedCode).orElseThrow().getTicketCategoryId(); - return makeSimpleReservation(eventName, request, redirectAttributes, locale, trimmedCode, event, ticketCategoryId); - } else { - return redirectToEvent; - } - }).orElse("redirect:/"); - } - - private String initReservationForHiddenCategory(String eventName, Model model, ServletWebRequest request, RedirectAttributes redirectAttributes, Locale locale, Event event, String redirectToEvent, int categoryId) { - Optional specialPrice = specialPriceRepository.findActiveNotAssignedByCategoryId(categoryId).stream().findFirst(); - if(specialPrice.isEmpty()) { - return redirectToEvent; - } - savePromoCode(eventName, specialPrice.get().getCode(), model, request.getRequest()); - return makeSimpleReservation(eventName, request, redirectAttributes, locale, specialPrice.get().getCode(), event, categoryId); - } - - - private String makeSimpleReservation(String eventName, ServletWebRequest request, RedirectAttributes redirectAttributes, Locale locale, String trimmedCode, Event event, int ticketCategoryId) { - ReservationForm form = new ReservationForm(); - form.setPromoCode(trimmedCode); - TicketReservationModification reservation = new TicketReservationModification(); - reservation.setAmount(1); - reservation.setTicketCategoryId(ticketCategoryId); - form.setReservation(Collections.singletonList(reservation)); - return validateAndReserve(eventName, form, new BeanPropertyBindingResult(form, "reservationForm"), request, redirectAttributes, locale, event); - } - - @RequestMapping(value = "/event/{eventName}/calendar/locale/{locale}", method = {RequestMethod.GET, RequestMethod.HEAD}) - public void calendar(@PathVariable("eventName") String eventName, - @PathVariable("locale") String locale, - @RequestParam(value = "type", required = false) String calendarType, - @RequestParam(value = "ticketId", required = false) String ticketId, - HttpServletResponse response) throws IOException { - Optional event = eventRepository.findOptionalByShortName(eventName); - if (event.isEmpty()) { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - - //meh - Event ev = event.get(); - - String description = eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(ev.getId(), EventDescription.EventDescriptionType.DESCRIPTION, locale).orElse(""); - TicketCategory category = ticketRepository.findOptionalByUUID(ticketId).map(t -> ticketCategoryRepository.getById(t.getCategoryId())).orElse(null); - - if("google".equals(calendarType)) { - response.sendRedirect(EventUtil.getGoogleCalendarURL(ev, category, description)); - } else { - Optional ical = EventUtil.getIcalForEvent(ev, category, description); - //meh, checked exceptions don't work well with Function & co :( - if(ical.isPresent()) { - response.setContentType("text/calendar"); - response.setHeader("Content-Disposition", "inline; filename=\"calendar.ics\""); - response.getOutputStream().write(ical.get()); - } else { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - } - } - - - @RequestMapping(value = "/event/{eventName}/reserve-tickets", method = { RequestMethod.POST, RequestMethod.GET, RequestMethod.HEAD }) - public String reserveTicket(@PathVariable("eventName") String eventName, - @ModelAttribute ReservationForm reservation, BindingResult bindingResult, - ServletWebRequest request, RedirectAttributes redirectAttributes, Locale locale) { - - return eventRepository.findOptionalByShortName(eventName).map(event -> { - if (request.getHttpMethod() == HttpMethod.GET) { - return "redirect:/event/" + eventName + "/"; - } else { - return validateAndReserve(eventName, reservation, bindingResult, request, redirectAttributes, locale, event); - } - }).orElse("redirect:/"); - - } - - private String validateAndReserve(String eventName, ReservationForm reservation, BindingResult bindingResult, ServletWebRequest request, RedirectAttributes redirectAttributes, Locale locale, Event event) { - - if(isCaptchaInvalid(reservation.getCaptcha(), request.getRequest(), event)) { - bindingResult.reject(ErrorsCode.STEP_2_CAPTCHA_VALIDATION_FAILED); - } - - final String redirectToEvent = "redirect:/event/" + eventName + "/"; - return reservation.validate(bindingResult, ticketReservationManager, additionalServiceRepository, eventManager, event) - .map(selected -> { - - Date expiration = DateUtils.addMinutes(new Date(), ticketReservationManager.getReservationTimeout(event)); - - try { - String reservationId = ticketReservationManager.createTicketReservation(event, - selected.getLeft(), selected.getRight(), expiration, - SessionUtil.retrieveSpecialPriceSessionId(request.getRequest()), - SessionUtil.retrievePromotionCodeDiscount(request.getRequest()), - locale, false); - return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/book"; - } catch (TicketReservationManager.NotEnoughTicketsException nete) { - bindingResult.reject(ErrorsCode.STEP_1_NOT_ENOUGH_TICKETS); - addToFlash(bindingResult, redirectAttributes); - return redirectToEvent; - } catch (TicketReservationManager.MissingSpecialPriceTokenException missing) { - bindingResult.reject(ErrorsCode.STEP_1_ACCESS_RESTRICTED); - addToFlash(bindingResult, redirectAttributes); - return redirectToEvent; - } catch (TicketReservationManager.InvalidSpecialPriceTokenException invalid) { - bindingResult.reject(ErrorsCode.STEP_1_CODE_NOT_FOUND); - addToFlash(bindingResult, redirectAttributes); - SessionUtil.cleanupSession(request.getRequest()); - return redirectToEvent; - } catch (TicketReservationManager.TooManyTicketsForDiscountCodeException tooMany) { - bindingResult.reject(ErrorsCode.STEP_2_DISCOUNT_CODE_USAGE_EXCEEDED); - addToFlash(bindingResult, redirectAttributes); - return redirectToEvent; - } - }).orElseGet(() -> { - addToFlash(bindingResult, redirectAttributes); - return redirectToEvent; - }); - } - - private SaleableAdditionalService getSaleableAdditionalService(Event event, Locale locale, AdditionalService as, PromoCodeDiscount promoCodeDiscount) { - return new SaleableAdditionalService(event, as, additionalServiceTextRepository.findBestMatchByLocaleAndType(as.getId(), locale.getLanguage(), AdditionalServiceText.TextType.TITLE).getValue(), - additionalServiceTextRepository.findBestMatchByLocaleAndType(as.getId(), locale.getLanguage(), AdditionalServiceText.TextType.DESCRIPTION).getValue(), promoCodeDiscount, 0); - } - - private static List adjustIndex(int offset, List l) { - List n = new ArrayList<>(l.size()); - - for(int i = 0; i < l.size(); i++) { - n.add(l.get(i).withIndex(i+offset)); - } - return n; - } - - private static boolean shouldApplyDiscount(PromoCodeDiscount promoCodeDiscount, TicketCategory ticketCategory) { - if(promoCodeDiscount.getCodeType() == PromoCodeDiscount.CodeType.DISCOUNT) { - return promoCodeDiscount.getCategories().isEmpty() || promoCodeDiscount.getCategories().contains(ticketCategory.getId()); - } - return ticketCategory.isAccessRestricted() && ticketCategory.getId() == promoCodeDiscount.getHiddenCategoryId(); - } - - private boolean isCaptchaInvalid(String recaptchaResponse, HttpServletRequest request, EventAndOrganizationId event) { - return configurationManager.isRecaptchaForTicketSelectionEnabled(event) - && !recaptchaService.checkRecaptcha(recaptchaResponse, request); - } - -} diff --git a/src/main/java/alfio/controller/IndexController.java b/src/main/java/alfio/controller/IndexController.java new file mode 100644 index 0000000000..7be24a7982 --- /dev/null +++ b/src/main/java/alfio/controller/IndexController.java @@ -0,0 +1,105 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see . + */ +package alfio.controller; + +import ch.digitalfondue.jfiveparse.*; +import lombok.AllArgsConstructor; +import org.springframework.context.ApplicationContext; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +@Controller +@AllArgsConstructor +public class IndexController { + + private final ApplicationContext applicationContext; + + @RequestMapping(value = "/", method = RequestMethod.HEAD) + public ResponseEntity replyToProxy() { + return ResponseEntity.ok("Up and running!"); + } + + @RequestMapping(value = "/healthz", method = RequestMethod.GET) + public ResponseEntity replyToK8s() { + return ResponseEntity.ok("Up and running!"); + } + + + //url defined in the angular app in app-routing.module.ts + /** +
+    { path: '', component: EventListComponent, canActivate: [LanguageGuard] },
+    { path: 'event/:eventShortName', component: EventDisplayComponent, canActivate: [LanguageGuard] },
+    { path: 'event/:eventShortName/reservation/:reservationId', component: ReservationComponent, canActivate: [LanguageGuard, ReservationGuard], children: [
+        { path: 'book', component: BookingComponent, canActivate: [ReservationGuard] },
+        { path: 'overview', component: OverviewComponent, canActivate: [ReservationGuard] },
+        { path: 'waitingPayment', redirectTo: 'waiting-payment'},
+        { path: 'waiting-payment', component: OfflinePaymentComponent, canActivate: [ReservationGuard] },
+        { path: 'processing-payment', component: ProcessingPaymentComponent, canActivate: [ReservationGuard] },
+        { path: 'success', component: SuccessComponent, canActivate: [ReservationGuard]}
+    ]},
+    { path: 'event/:eventShortName/ticket/:ticketId/view', component: ViewTicketComponent, canActivate: [LanguageGuard] }
+    
+ + */ + @RequestMapping(value = { + "/", + "/event/{eventShortName}", + "/event/{eventShortName}/reservation/{reservationId}/book", + "/event/{eventShortName}/reservation/{reservationId}/overview", + "/event/{eventShortName}/reservation/{reservationId}/waitingPayment", + "/event/{eventShortName}/reservation/{reservationId}/waiting-payment", + "/event/{eventShortName}/reservation/{reservationId}/processing-payment", + "/event/{eventShortName}/reservation/{reservationId}/success", + "/event/{eventShortName}/ticket/{ticketId}/view" + }, method = RequestMethod.GET) + public void replyToIndex(HttpServletResponse response) throws IOException { + + // FIXME, do this at compile time... + // fetch version of alfio-public-frontend, do the transformation, etc... + final String basePath = "webjars/alfio-public-frontend/8fd67bc9f6/alfio-public-frontend/"; + var resource = applicationContext.getResource(basePath + "index.html"); + + response.setContentType("text/html"); + response.setCharacterEncoding("UTF-8"); + + Parser p = new Parser(); + try(var is = resource.getInputStream(); var isr = new InputStreamReader(is, StandardCharsets.UTF_8); var os = response.getOutputStream()) { + Document d = p.parse(isr); + NodeMatcher scriptNodes = Selector.select().element("script").toMatcher(); + + d.getAllNodesMatching(scriptNodes).stream().map(Element.class::cast).forEach(elem -> { + elem.setAttribute("src", basePath + elem.getAttribute("src")); + }); + + NodeMatcher cssNodes = Selector.select().element("link").attrValEq("rel", "stylesheet").toMatcher(); + d.getAllNodesMatching(cssNodes).stream().map(Element.class::cast).forEach(elem -> { + elem.setAttribute("href", basePath + elem.getAttribute("href")); + }); + + StreamUtils.copy(d.getOuterHTML(), StandardCharsets.UTF_8, os); + } + } +} diff --git a/src/main/java/alfio/controller/InvoiceReceiptController.java b/src/main/java/alfio/controller/InvoiceReceiptController.java deleted file mode 100644 index fe0ccfab66..0000000000 --- a/src/main/java/alfio/controller/InvoiceReceiptController.java +++ /dev/null @@ -1,119 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller; - -import alfio.controller.support.TemplateProcessor; -import alfio.manager.ExtensionManager; -import alfio.manager.FileUploadManager; -import alfio.manager.TicketReservationManager; -import alfio.manager.system.ConfigurationManager; -import alfio.model.Event; -import alfio.model.EventAndOrganizationId; -import alfio.model.TicketReservation; -import alfio.repository.EventRepository; -import alfio.util.FileUtil; -import alfio.util.TemplateManager; -import alfio.util.TemplateResource; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; - -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Locale; -import java.util.Map; -import java.util.function.BiFunction; - -@Controller -@RequiredArgsConstructor -public class InvoiceReceiptController { - - private final EventRepository eventRepository; - private final TicketReservationManager ticketReservationManager; - private final FileUploadManager fileUploadManager; - private final TemplateManager templateManager; - private final ConfigurationManager configurationManager; - private final ExtensionManager extensionManager; - - private ResponseEntity handleReservationWith(String eventName, String reservationId, Authentication authentication, - BiFunction> with) { - ResponseEntity notFound = ResponseEntity.notFound().build(); - ResponseEntity badRequest = ResponseEntity.badRequest().build(); - - - - return eventRepository.findOptionalByShortName(eventName).map(event -> { - if(canAccessReceiptOrInvoice(event, authentication)) { - return ticketReservationManager.findById(reservationId).map(ticketReservation -> with.apply(event, ticketReservation)).orElse(notFound); - } else { - return badRequest; - } - } - ).orElse(notFound); - } - - private boolean canAccessReceiptOrInvoice(EventAndOrganizationId event, Authentication authentication) { - return configurationManager.canGenerateReceiptOrInvoiceToCustomer(event) || !isAnonymous(authentication); - } - - - private boolean isAnonymous(Authentication authentication) { - return authentication == null || - authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).anyMatch("ROLE_ANONYMOUS"::equals); - } - - @RequestMapping("/event/{eventName}/reservation/{reservationId}/receipt") - public ResponseEntity getReceipt(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - HttpServletResponse response, - Authentication authentication) { - return handleReservationWith(eventName, reservationId, authentication, generatePdfFunction(false, response)); - } - - @RequestMapping("/event/{eventName}/reservation/{reservationId}/invoice") - public ResponseEntity getInvoice(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - HttpServletResponse response, - Authentication authentication) { - return handleReservationWith(eventName, reservationId, authentication, generatePdfFunction(true, response)); - } - - private BiFunction> generatePdfFunction(boolean forInvoice, HttpServletResponse response) { - return (event, reservation) -> { - if(forInvoice ^ reservation.getInvoiceNumber() != null || reservation.isCancelled()) { - return ResponseEntity.notFound().build(); - } - - Map billingModel = ticketReservationManager.getOrCreateBillingDocumentModel(event, reservation, null); - - try { - FileUtil.sendHeaders(response, event.getShortName(), reservation.getId(), forInvoice ? "invoice" : "receipt"); - TemplateProcessor.buildReceiptOrInvoicePdf(event, fileUploadManager, new Locale(reservation.getUserLanguage()), - templateManager, billingModel, forInvoice ? TemplateResource.INVOICE_PDF : TemplateResource.RECEIPT_PDF, - extensionManager, response.getOutputStream()); - return ResponseEntity.ok(null); - } catch (IOException ioe) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); - } - }; - } -} diff --git a/src/main/java/alfio/controller/ReservationController.java b/src/main/java/alfio/controller/ReservationController.java deleted file mode 100644 index c54207d68d..0000000000 --- a/src/main/java/alfio/controller/ReservationController.java +++ /dev/null @@ -1,764 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller; - -import alfio.controller.api.support.TicketHelper; -import alfio.controller.form.ContactAndTicketsForm; -import alfio.controller.form.PaymentForm; -import alfio.controller.form.UpdateTicketOwnerForm; -import alfio.controller.support.SessionUtil; -import alfio.controller.support.TicketDecorator; -import alfio.manager.*; -import alfio.manager.payment.PaymentSpecification; -import alfio.manager.payment.StripeCreditCardManager; -import alfio.manager.support.PaymentResult; -import alfio.manager.system.ConfigurationManager; -import alfio.manager.system.ReservationPriceCalculator; -import alfio.model.*; -import alfio.model.TicketReservation.TicketReservationStatus; -import alfio.model.TicketReservationInvoicingAdditionalInfo.ItalianEInvoicing; -import alfio.model.result.ValidationResult; -import alfio.model.system.Configuration; -import alfio.model.system.ConfigurationKeys; -import alfio.model.transaction.PaymentContext; -import alfio.model.transaction.PaymentProxy; -import alfio.model.transaction.PaymentToken; -import alfio.repository.EventRepository; -import alfio.repository.TicketFieldRepository; -import alfio.repository.TicketReservationRepository; -import alfio.repository.user.OrganizationRepository; -import alfio.util.ErrorsCode; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; -import org.springframework.context.MessageSource; -import org.springframework.context.MessageSourceResolvable; -import org.springframework.context.support.DefaultMessageSourceResolvable; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.validation.Errors; -import org.springframework.validation.ValidationUtils; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springframework.web.servlet.support.RequestContextUtils; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import java.time.ZonedDateTime; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static alfio.model.PriceContainer.VatStatus.*; -import static alfio.model.system.Configuration.getSystemConfiguration; -import static alfio.model.system.ConfigurationKeys.*; -import static alfio.util.MonetaryUtil.unitToCents; -import static java.util.stream.Collectors.toList; - -@Controller -@Log4j2 -@AllArgsConstructor -public class ReservationController { - - private final EventRepository eventRepository; - private final EventManager eventManager; - private final TicketReservationManager ticketReservationManager; - private final OrganizationRepository organizationRepository; - - private final MessageSource messageSource; - private final ConfigurationManager configurationManager; - private final TicketHelper ticketHelper; - private final TicketFieldRepository ticketFieldRepository; - private final PaymentManager paymentManager; - private final EuVatChecker vatChecker; - private final RecaptchaService recaptchaService; - private final TicketReservationRepository ticketReservationRepository; - private final ExtensionManager extensionManager; - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/book", method = RequestMethod.GET) - public String showBookingPage(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - Model model, - Locale locale) { - - return eventRepository.findOptionalByShortName(eventName) - .map(event -> ticketReservationManager.findById(reservationId) - .map(reservation -> { - - if (reservation.getStatus() != TicketReservationStatus.PENDING) { - return redirectReservation(Optional.of(reservation), eventName, reservationId); - } - - TicketReservationAdditionalInfo additionalInfo = ticketReservationRepository.getAdditionalInfo(reservationId); - if (additionalInfo.hasBeenValidated()) { - return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/overview"; - } - - Function partialConfig = Configuration.from(event); - - Configuration.ConfigurationPathKey forceAssignmentKey = partialConfig.apply(FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION); - boolean forceAssignment = configurationManager.getBooleanConfigValue(forceAssignmentKey, false); - - List ticketsInReservation = ticketReservationManager.findTicketsInReservation(reservationId); - - model.addAttribute("postponeAssignment", false) - .addAttribute("showPostpone", !forceAssignment && ticketsInReservation.size() > 1 && !ticketReservationManager.containsCategoriesLinkedToGroups(reservationId, event.getId())); - - - OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event); - - //FIXME recaptcha for free orders - - boolean invoiceAllowed = configurationManager.hasAllConfigurationsForInvoice(event) || vatChecker.isReverseChargeEnabledFor(event.getOrganizationId()); - boolean onlyInvoice = invoiceAllowed && configurationManager.isInvoiceOnly(event); - - - ContactAndTicketsForm contactAndTicketsForm = ContactAndTicketsForm.fromExistingReservation(reservation, additionalInfo); - model.addAttribute("orderSummary", orderSummary) - .addAttribute("reservationId", reservationId) - .addAttribute("reservation", reservation) - .addAttribute("pageTitle", "reservation-page.header.title") - .addAttribute("event", event) - .addAttribute("expressCheckoutEnabled", isExpressCheckoutEnabled(event, orderSummary)) - .addAttribute("useFirstAndLastName", event.mustUseFirstAndLastName()) - .addAttribute("countries", TicketHelper.getLocalizedCountries(locale)) - .addAttribute("countriesForVat", TicketHelper.getLocalizedCountriesForVat(locale)) - .addAttribute("euCountriesForVat", TicketHelper.getLocalizedEUCountriesForVat(locale, configurationManager.getRequiredValue(getSystemConfiguration(ConfigurationKeys.EU_COUNTRIES_LIST)))) - .addAttribute("euVatCheckingEnabled", vatChecker.isReverseChargeEnabledFor(event.getOrganizationId())) - .addAttribute("invoiceIsAllowed", !orderSummary.getFree() && invoiceAllowed) - .addAttribute("onlyInvoice", !orderSummary.getFree() && onlyInvoice) - .addAttribute("vatNrIsLinked", orderSummary.isVatExempt() || contactAndTicketsForm.getHasVatCountryCode()) - .addAttribute("attendeeAutocompleteEnabled", ticketsInReservation.size() == 1 && configurationManager.getBooleanConfigValue(partialConfig.apply(ENABLE_ATTENDEE_AUTOCOMPLETE), true)) - .addAttribute("billingAddressLabel", invoiceAllowed ? "reservation-page.billing-address" : "reservation-page.receipt-address") - .addAttribute("customerReferenceEnabled", configurationManager.getBooleanConfigValue(partialConfig.apply(ENABLE_CUSTOMER_REFERENCE), false)) - .addAttribute("enabledItalyEInvoicing", configurationManager.getBooleanConfigValue(partialConfig.apply(ENABLE_ITALY_E_INVOICING), false)) - .addAttribute("vatNumberStrictlyRequired", configurationManager.getBooleanConfigValue(partialConfig.apply(VAT_NUMBER_IS_REQUIRED), false)); - - Map modelMap = model.asMap(); - modelMap.putIfAbsent("paymentForm", contactAndTicketsForm); - modelMap.putIfAbsent("hasErrors", false); - - boolean hasPaidSupplement = ticketReservationManager.hasPaidSupplements(reservationId); - model.addAttribute( - "ticketsByCategory", - ticketsInReservation.stream().collect(Collectors.groupingBy(Ticket::getCategoryId)).entrySet().stream() - .map((e) -> { - TicketCategory category = eventManager.getTicketCategoryById(e.getKey(), event.getId()); - List decorators = TicketDecorator.decorate(e.getValue(), - !hasPaidSupplement && configurationManager.getBooleanConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), category.getId(), ALLOW_FREE_TICKETS_CANCELLATION), false), - eventManager.checkTicketCancellationPrerequisites(), - ticketHelper::findTicketFieldConfigurationAndValue, - true, (t) -> "tickets["+t.getUuid()+"]."); - return Pair.of(category, decorators); - }) - .collect(toList())); - return "/event/reservation-page"; - }).orElseGet(() -> redirectReservation(Optional.empty(), eventName, reservationId))) - .orElse("redirect:/"); - } - - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/success", method = RequestMethod.GET) - public String showConfirmationPage(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - @RequestParam(value = "confirmation-email-sent", required = false, defaultValue = "false") boolean confirmationEmailSent, - @RequestParam(value = "ticket-email-sent", required = false, defaultValue = "false") boolean ticketEmailSent, - Model model, - Locale locale, - HttpServletRequest request) { - - return eventRepository.findOptionalByShortName(eventName).map(ev -> { - Optional tr = ticketReservationManager.findById(reservationId); - return tr.filter(r -> r.getStatus() == TicketReservationStatus.COMPLETE) - .map(reservation -> { - SessionUtil.cleanupSession(request); - model.addAttribute("reservationId", reservationId); - model.addAttribute("reservation", reservation); - model.addAttribute("confirmationEmailSent", confirmationEmailSent); - model.addAttribute("ticketEmailSent", ticketEmailSent); - - List tickets = ticketReservationManager.findTicketsInReservation(reservationId); - List, AdditionalServiceItem>> additionalServices = ticketReservationManager.findAdditionalServicesInReservation(reservationId) - .stream() - .map(t -> Triple.of(t.getLeft(), t.getMiddle().stream().filter(d -> d.getLocale().equals(locale.getLanguage())).collect(toList()), t.getRight())) - .collect(Collectors.toList()); - boolean hasPaidSupplement = ticketReservationManager.hasPaidSupplements(reservationId); - model.addAttribute( - "ticketsByCategory", - tickets.stream().collect(Collectors.groupingBy(Ticket::getCategoryId)).entrySet().stream() - .map((e) -> { - TicketCategory category = eventManager.getTicketCategoryById(e.getKey(), ev.getId()); - List decorators = TicketDecorator.decorate(e.getValue(), - !hasPaidSupplement && configurationManager.getBooleanConfigValue(Configuration.from(ev.getOrganizationId(), ev.getId(), category.getId(), ALLOW_FREE_TICKETS_CANCELLATION), false), - eventManager.checkTicketCancellationPrerequisites(), - ticketHelper::findTicketFieldConfigurationAndValue, - tickets.size() == 1, TicketDecorator.EMPTY_PREFIX_GENERATOR); - return Pair.of(category, decorators); - }) - .collect(toList())); - boolean ticketsAllAssigned = tickets.stream().allMatch(Ticket::getAssigned); - model.addAttribute("ticketsAreAllAssigned", ticketsAllAssigned) - .addAttribute("displayTransferInfo", ticketsAllAssigned && configurationManager.getBooleanConfigValue(Configuration.from(ev).apply(ENABLE_TICKET_TRANSFER), true)) - .addAttribute("collapseEnabled", tickets.size() > 1 && !ticketsAllAssigned) - .addAttribute("additionalServicesOnly", tickets.isEmpty() && !additionalServices.isEmpty()) - .addAttribute("additionalServices", additionalServices) - .addAttribute("countries", TicketHelper.getLocalizedCountries(locale)) - .addAttribute("pageTitle", "reservation-page-complete.header.title") - .addAttribute("event", ev) - .addAttribute("useFirstAndLastName", ev.mustUseFirstAndLastName()) - .addAttribute("userCanDownloadReceiptOrInvoice", configurationManager.canGenerateReceiptOrInvoiceToCustomer(ev)); - - model.asMap().putIfAbsent("validationResult", ValidationResult.success()); - - return "/event/reservation-page-complete"; - }).orElseGet(() -> redirectReservation(tr, eventName, reservationId)); - }).orElse("redirect:/"); - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/validate-to-overview", method = RequestMethod.POST) - public String validateToOverview(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, - ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, - HttpServletRequest request, RedirectAttributes redirectAttributes, Locale locale) { - - Optional eventOptional = eventRepository.findOptionalByShortName(eventName); - return redirectIfNotValid(contactAndTicketsForm.isBackFromOverview(), contactAndTicketsForm.shouldCancelReservation(), eventName, reservationId, request, eventOptional) - .orElseGet(() -> { - Event event = eventOptional.orElseThrow(); - - - var reservation = ticketReservationManager.findById(reservationId).orElseThrow(); - final TotalPrice reservationCost = ticketReservationManager.totalReservationCostWithVAT(reservation.withVatStatus(event.getVatStatus())); - Configuration.ConfigurationPathKey forceAssignmentKey = Configuration.from(event, ConfigurationKeys.FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION); - boolean forceAssignment = configurationManager.getBooleanConfigValue(forceAssignmentKey, false); - - if(forceAssignment || ticketReservationManager.containsCategoriesLinkedToGroups(reservationId, event.getId())) { - contactAndTicketsForm.setPostponeAssignment(false); - } - - boolean invoiceOnly = configurationManager.isInvoiceOnly(event); - - if(invoiceOnly && reservationCost.getPriceWithVAT() > 0) { - //override, that's why we save it - contactAndTicketsForm.setInvoiceRequested(true); - } - - CustomerName customerName = new CustomerName(contactAndTicketsForm.getFullName(), contactAndTicketsForm.getFirstName(), contactAndTicketsForm.getLastName(), event.mustUseFirstAndLastName(), false); - - - ticketReservationRepository.resetVat(reservationId, contactAndTicketsForm.isInvoiceRequested(), event.getVatStatus(), - reservation.getSrcPriceCts(), reservationCost.getPriceWithVAT(), reservationCost.getVAT(), Math.abs(reservationCost.getDiscount()), reservation.getCurrencyCode()); - if(contactAndTicketsForm.isBusiness()) { - checkAndApplyVATRules(eventName, reservationId, contactAndTicketsForm, bindingResult, event); - } - - //persist data - ticketReservationManager.updateReservation(reservationId, customerName, contactAndTicketsForm.getEmail(), - contactAndTicketsForm.getBillingAddressCompany(), contactAndTicketsForm.getBillingAddressLine1(), contactAndTicketsForm.getBillingAddressLine2(), - contactAndTicketsForm.getBillingAddressZip(), contactAndTicketsForm.getBillingAddressCity(), contactAndTicketsForm.getVatCountryCode(), - contactAndTicketsForm.getCustomerReference(), contactAndTicketsForm.getVatNr(), contactAndTicketsForm.isInvoiceRequested(), - contactAndTicketsForm.getAddCompanyBillingDetails(), contactAndTicketsForm.canSkipVatNrCheck(), false, locale); - - boolean italyEInvoicing = configurationManager.getBooleanConfigValue(Configuration.from(event, ENABLE_ITALY_E_INVOICING), false); - - if(italyEInvoicing) { - ticketReservationManager.updateReservationInvoicingAdditionalInformation(reservationId, - new TicketReservationInvoicingAdditionalInfo(getItalianInvoicingInfo(contactAndTicketsForm)) - ); - } - - assignTickets(event.getShortName(), reservationId, contactAndTicketsForm, bindingResult, request, true, true); - // - - Map formValidationParameters = Collections.singletonMap(ENABLE_ITALY_E_INVOICING, italyEInvoicing); - // - contactAndTicketsForm.validate(bindingResult, event, - ticketFieldRepository.findAdditionalFieldsForEvent(event.getId()), - new SameCountryValidator(configurationManager, extensionManager, event.getOrganizationId(), event.getId(), reservationId, vatChecker), - formValidationParameters); - // - - if(!bindingResult.hasErrors()) { - extensionManager.handleReservationValidation(event, reservation, contactAndTicketsForm, bindingResult); - } - - if(bindingResult.hasErrors()) { - SessionUtil.addToFlash(bindingResult, redirectAttributes); - return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/book"; - } - ticketReservationRepository.updateValidationStatus(reservationId, true); - - - return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/overview"; - }); - - } - - private ItalianEInvoicing getItalianInvoicingInfo(ContactAndTicketsForm contactAndTicketsForm) { - if("IT".equalsIgnoreCase(contactAndTicketsForm.getVatCountryCode())) { - return new ItalianEInvoicing(contactAndTicketsForm.getItalyEInvoicingFiscalCode(), - contactAndTicketsForm.getItalyEInvoicingReferenceType(), - contactAndTicketsForm.getItalyEInvoicingReferenceAddresseeCode(), - contactAndTicketsForm.getItalyEInvoicingReferencePEC()); - } - return null; - } - - private void checkAndApplyVATRules(String eventName, String reservationId, ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, Event event) { - // VAT handling - String country = contactAndTicketsForm.getVatCountryCode(); - - // validate VAT presence if EU mode is enabled - if(vatChecker.isReverseChargeEnabledFor(event.getOrganizationId()) && isEUCountry(country)) { - ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "vatNr", "error.emptyField"); - } - - try { - Optional vatDetail = eventRepository.findOptionalByShortName(eventName) - .flatMap(e -> ticketReservationRepository.findOptionalReservationById(reservationId).map(r -> Pair.of(e, r))) - .filter(e -> EnumSet.of(INCLUDED, NOT_INCLUDED).contains(e.getKey().getVatStatus())) - .filter(e -> vatChecker.isReverseChargeEnabledFor(e.getKey().getOrganizationId())) - .flatMap(e -> vatChecker.checkVat(contactAndTicketsForm.getVatNr(), country, e.getKey())); - - - vatDetail.ifPresent(vatValidation -> { - if (!vatValidation.isValid()) { - bindingResult.rejectValue("vatNr", "error.vat"); - } else { - var reservation = ticketReservationManager.findById(reservationId).orElseThrow(); - PriceContainer.VatStatus vatStatus = determineVatStatus(event.getVatStatus(), vatValidation.isVatExempt()); - var updatedPrice = ticketReservationManager.totalReservationCostWithVAT(reservation.withVatStatus(vatStatus));// update VatStatus to the new value for calculating the new price - var calculator = new ReservationPriceCalculator(reservation.withVatStatus(vatStatus), updatedPrice, ticketReservationManager.findTicketsInReservation(reservationId), event); - ticketReservationRepository.updateBillingData(vatStatus, reservation.getSrcPriceCts(), - unitToCents(calculator.getFinalPrice()), unitToCents(calculator.getVAT()), unitToCents(calculator.getAppliedDiscount()), - reservation.getCurrencyCode(), StringUtils.trimToNull(vatValidation.getVatNr()), - country, contactAndTicketsForm.isInvoiceRequested(), reservationId); - vatChecker.logSuccessfulValidation(vatValidation, reservationId, event.getId()); - } - }); - } catch (IllegalStateException ise) {//vat checker failure - bindingResult.rejectValue("vatNr", "error.vatVIESDown"); - } - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/overview", method = RequestMethod.GET) - public String showOverview(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - Locale locale, - Model model, - HttpSession session) { - - return eventRepository.findOptionalByShortName(eventName) - .map(event -> ticketReservationManager.findById(reservationId) - .map(reservation -> { - if (reservation.getStatus() != TicketReservationStatus.PENDING) { - return redirectReservation(Optional.of(reservation), eventName, reservationId); - } - TicketReservationAdditionalInfo additionalInfo = ticketReservationRepository.getAdditionalInfo(reservationId); - if (!additionalInfo.hasBeenValidated()) { - return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/book"; - } - - OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event); - - List activePaymentMethods; - if(session.getAttribute(PaymentManager.PAYMENT_TOKEN) != null) { - model.addAttribute("tokenAcquired", true); - activePaymentMethods = Collections.singletonList(((PaymentToken)session.getAttribute(PaymentManager.PAYMENT_TOKEN)).getPaymentProvider()); - } else { - activePaymentMethods = paymentManager.getPaymentMethods(event) - .stream() - .filter(p -> TicketReservationManager.isValidPaymentMethod(p, event, configurationManager)) - .map(PaymentManager.PaymentMethodDTO::getPaymentProxy) - .collect(toList()); - } - - model.addAllAttributes(paymentManager.loadModelOptionsFor(activePaymentMethods, event)); - - model.addAttribute("multiplePaymentMethods" , activePaymentMethods.size() > 1 ) - .addAttribute("activePaymentMethods", activePaymentMethods); - - model.addAttribute("orderSummary", orderSummary) - .addAttribute("reservationId", reservationId) - .addAttribute("reservation", reservation) - .addAttribute("pageTitle", "reservation-page.header.title") - .addAttribute("event", event) - .addAttribute("billingDetails", ticketReservationRepository.getBillingDetailsForReservation(reservationId)) - .addAttribute("displayInvoiceData", !orderSummary.getFree() && reservation.isInvoiceRequested()); - return "/event/overview"; - }).orElseGet(() -> redirectReservation(Optional.empty(), eventName, reservationId))) - .orElse("redirect:/"); - } - - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/failure", method = RequestMethod.GET) - public String showFailurePage(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - @RequestParam(value = "confirmation-email-sent", required = false, defaultValue = "false") boolean confirmationEmailSent, - @RequestParam(value = "ticket-email-sent", required = false, defaultValue = "false") boolean ticketEmailSent, - Model model) { - Optional event = eventRepository.findOptionalByShortName(eventName); - if (event.isEmpty()) { - return "redirect:/"; - } - - Optional reservation = ticketReservationManager.findById(reservationId); - Optional status = reservation.map(TicketReservation::getStatus); - - if(status.isEmpty()) { - return redirectReservation(reservation, eventName, reservationId); - } - - TicketReservationStatus ticketReservationStatus = status.get(); - if(ticketReservationStatus == TicketReservationStatus.IN_PAYMENT || ticketReservationStatus == TicketReservationStatus.STUCK) { - model.addAttribute("reservation", reservation.get()); - model.addAttribute("organizer", organizationRepository.getById(event.get().getOrganizationId())); - model.addAttribute("pageTitle", "reservation-page-error-status.header.title"); - model.addAttribute("event", event.get()); - return "/event/reservation-page-error-status"; - } - - return redirectReservation(reservation, eventName, reservationId); - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}", method = RequestMethod.GET) - public String showReservationPage(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - Model model) { - Optional event = eventRepository.findOptionalByShortName(eventName); - if (event.isEmpty()) { - return "redirect:/"; - } - - return redirectReservation(ticketReservationManager.findById(reservationId), eventName, reservationId); - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/notfound", method = RequestMethod.GET) - public String showNotFoundPage(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - Model model) { - - Optional event = eventRepository.findOptionalByShortName(eventName); - if (event.isEmpty()) { - return "redirect:/"; - } - - Optional reservation = ticketReservationManager.findById(reservationId); - - if(reservation.isEmpty()) { - model.addAttribute("reservationId", reservationId); - model.addAttribute("pageTitle", "reservation-page-not-found.header.title"); - model.addAttribute("event", event.get()); - return "/event/reservation-page-not-found"; - } - - return redirectReservation(reservation, eventName, reservationId); - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/waitingPayment", method = RequestMethod.GET) - public String showWaitingPaymentPage(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - Model model, Locale locale) { - - Optional event = eventRepository.findOptionalByShortName(eventName); - if (event.isEmpty()) { - return "redirect:/"; - } - - Optional reservation = ticketReservationManager.findById(reservationId); - TicketReservationStatus status = reservation.map(TicketReservation::getStatus).orElse(TicketReservationStatus.PENDING); - if(reservation.isPresent() && status == TicketReservationStatus.OFFLINE_PAYMENT) { - Event ev = event.get(); - TicketReservation ticketReservation = reservation.get(); - OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, ev); - model.addAttribute("totalPrice", orderSummary.getTotalPrice()); - model.addAttribute("emailAddress", organizationRepository.getById(ev.getOrganizationId()).getEmail()); - model.addAttribute("reservation", ticketReservation); - model.addAttribute("paymentReason", ev.getShortName() + " " + ticketReservationManager.getShortReservationID(ev, ticketReservation)); - model.addAttribute("pageTitle", "reservation-page-waiting.header.title"); - model.addAttribute("bankAccount", configurationManager.getStringConfigValue(Configuration.from(ev, BANK_ACCOUNT_NR)).orElse("")); - - - Optional maybeAccountOwner = configurationManager.getStringConfigValue(Configuration.from(ev, BANK_ACCOUNT_OWNER)); - model.addAttribute("hasBankAccountOwnerSet", maybeAccountOwner.isPresent()); - model.addAttribute("bankAccountOwner", Arrays.asList(maybeAccountOwner.orElse("").split("\n"))); - - model.addAttribute("expires", ZonedDateTime.ofInstant(ticketReservation.getValidity().toInstant(), ev.getZoneId())); - model.addAttribute("event", ev); - model.addAttribute("userCanDownloadReceiptOrInvoice", configurationManager.canGenerateReceiptOrInvoiceToCustomer(ev)); - return "/event/reservation-waiting-for-payment"; - } - - return redirectReservation(reservation, eventName, reservationId); - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/processing-payment", method = RequestMethod.GET) - public String showProcessingPayment(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - Model model, Locale locale) { - - Optional event = eventRepository.findOptionalByShortName(eventName); - if (event.isEmpty()) { - return "redirect:/"; - } - - Optional reservation = ticketReservationManager.findById(reservationId); - TicketReservationStatus status = reservation.map(TicketReservation::getStatus).orElse(TicketReservationStatus.PENDING); - if(reservation.isPresent() && (status == TicketReservationStatus.EXTERNAL_PROCESSING_PAYMENT || status == TicketReservationStatus.WAITING_EXTERNAL_CONFIRMATION)) { - Event ev = event.get(); - TicketReservation ticketReservation = reservation.get(); - OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, ev); - - model.addAttribute("orderSummary", orderSummary) - .addAttribute("reservationId", reservationId) - .addAttribute("reservation", ticketReservation) - .addAttribute("pageTitle", "reservation-page.header.title") - .addAttribute("paymentMethod", paymentManager.getPaymentMethodForReservation(ticketReservation)) - .addAttribute("event", ev); - - return "/event/reservation-processing-payment"; - } - - return redirectReservation(reservation, eventName, reservationId); - } - - - public String redirectReservation(Optional ticketReservation, String eventName, String reservationId) { - String baseUrl = "redirect:/event/" + eventName + "/reservation/" + reservationId; - if(ticketReservation.isEmpty()) { - return baseUrl + "/notfound"; - } - TicketReservation reservation = ticketReservation.get(); - - switch(reservation.getStatus()) { - case PENDING: - TicketReservationAdditionalInfo additionalInfo = ticketReservationRepository.getAdditionalInfo(reservationId); - return additionalInfo.hasBeenValidated() ? baseUrl + "/overview" : baseUrl + "/book"; - case COMPLETE: - return baseUrl + "/success"; - case OFFLINE_PAYMENT: - return baseUrl + "/waitingPayment"; - case EXTERNAL_PROCESSING_PAYMENT: - case WAITING_EXTERNAL_CONFIRMATION: - return baseUrl + "/processing-payment"; - case IN_PAYMENT: - case STUCK: - return baseUrl + "/failure"; - } - - return "redirect:/"; - } - - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}", method = RequestMethod.POST) - public String handleReservation(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - PaymentForm paymentForm, - BindingResult bindingResult, - Model model, - HttpServletRequest request, - Locale locale, - RedirectAttributes redirectAttributes, - HttpSession session) { - - Optional eventOptional = eventRepository.findOptionalByShortName(eventName); - - return redirectIfNotValid(paymentForm.isBackFromOverview(), paymentForm.shouldCancelReservation(), eventName, reservationId, request, eventOptional) - .orElseGet(() -> { - Event event = eventOptional.orElseThrow(IllegalStateException::new); - Optional optionalReservation = ticketReservationManager.findById(reservationId); - if (optionalReservation.isEmpty()) { - return redirectReservation(optionalReservation, eventName, reservationId); - } - if (paymentForm.shouldCancelReservation()) { - ticketReservationManager.cancelPendingReservation(reservationId, false, null); - SessionUtil.cleanupSession(request); - return "redirect:/event/" + eventName + "/"; - } - if (!optionalReservation.get().getValidity().after(new Date())) { - bindingResult.reject(ErrorsCode.STEP_2_ORDER_EXPIRED); - } - - final TicketReservation ticketReservation = optionalReservation.get(); - - final TotalPrice reservationCost = ticketReservationManager.totalReservationCostWithVAT(reservationId); - - paymentForm.validate(bindingResult, event, reservationCost); - if (bindingResult.hasErrors()) { - SessionUtil.addToFlash(bindingResult, redirectAttributes); - return redirectReservation(optionalReservation, eventName, reservationId); - } - - if(isCaptchaInvalid(reservationCost.getPriceWithVAT(), paymentForm.getPaymentMethod(), request, event)) { - log.debug("captcha validation failed."); - bindingResult.reject(ErrorsCode.STEP_2_CAPTCHA_VALIDATION_FAILED); - } - - if(!bindingResult.hasErrors()) { - extensionManager.handleReservationValidation(event, ticketReservation, paymentForm, bindingResult); - } - - if (bindingResult.hasErrors()) { - SessionUtil.addToFlash(bindingResult, redirectAttributes); - return redirectReservation(Optional.of(ticketReservation), eventName, reservationId); - } - - CustomerName customerName = new CustomerName(ticketReservation.getFullName(), ticketReservation.getFirstName(), ticketReservation.getLastName(), event.mustUseFirstAndLastName()); - - OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event); - - PaymentToken paymentToken = (PaymentToken) session.getAttribute(PaymentManager.PAYMENT_TOKEN); - if(paymentToken == null && StringUtils.isNotEmpty(paymentForm.getGatewayToken())) { - paymentToken = paymentManager.buildPaymentToken(paymentForm.getGatewayToken(), paymentForm.getPaymentMethod(), new PaymentContext(event, reservationId)); - } - PaymentSpecification spec = new PaymentSpecification(reservationId, paymentToken, reservationCost.getPriceWithVAT(), - event, ticketReservation.getEmail(), customerName, ticketReservation.getBillingAddress(), ticketReservation.getCustomerReference(), - locale, ticketReservation.isInvoiceRequested(), !ticketReservation.isDirectAssignmentRequested(), - orderSummary, ticketReservation.getVatCountryCode(), ticketReservation.getVatNr(), ticketReservation.getVatStatus(), - Boolean.TRUE.equals(paymentForm.getTermAndConditionsAccepted()), Boolean.TRUE.equals(paymentForm.getPrivacyPolicyAccepted())); - - final PaymentResult status = ticketReservationManager.performPayment(spec, reservationCost, SessionUtil.retrieveSpecialPriceSessionId(request), - Optional.ofNullable(paymentForm.getPaymentMethod())); - - // - model.addAttribute("paymentResultStatus", status); - // - - if (status.isRedirect()) { - return "redirect:" + status.getRedirectUrl(); - } - - if(!status.isSuccessful()) { - String errorMessageCode = status.getErrorCode().orElse(StripeCreditCardManager.STRIPE_UNEXPECTED); - MessageSourceResolvable message = new DefaultMessageSourceResolvable(new String[]{errorMessageCode, StripeCreditCardManager.STRIPE_UNEXPECTED}); - bindingResult.reject(ErrorsCode.STEP_2_PAYMENT_PROCESSING_ERROR, new Object[]{messageSource.getMessage(message, locale)}, null); - SessionUtil.addToFlash(bindingResult, redirectAttributes); - SessionUtil.removePaymentToken(request); - return redirectReservation(optionalReservation, eventName, reservationId); - } - - // - SessionUtil.cleanupSession(request); - - return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/success"; - }); - } - - private boolean isCaptchaInvalid(int cost, PaymentProxy paymentMethod, HttpServletRequest request, EventAndOrganizationId event) { - return (cost == 0 || paymentMethod == PaymentProxy.OFFLINE || paymentMethod == PaymentProxy.ON_SITE) - && configurationManager.isRecaptchaForOfflinePaymentEnabled(event) - && !recaptchaService.checkRecaptcha(null, request); - } - - private void assignTickets(String eventName, String reservationId, ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, HttpServletRequest request, boolean preAssign, boolean skipValidation) { - if(!contactAndTicketsForm.isPostponeAssignment()) { - contactAndTicketsForm.getTickets().forEach((ticketId, owner) -> { - if (preAssign) { - Optional bindingResultOptional = skipValidation ? Optional.empty() : Optional.of(bindingResult); - ticketHelper.preAssignTicket(eventName, reservationId, ticketId, owner, bindingResultOptional, request, (tr) -> { - }, Optional.empty()); - } else { - ticketHelper.assignTicket(eventName, ticketId, owner, Optional.of(bindingResult), request, (tr) -> { - }, Optional.empty(), true); - } - }); - } - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/re-send-email", method = RequestMethod.POST) - public String reSendReservationConfirmationEmail(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, HttpServletRequest request) { - - Optional event = eventRepository.findOptionalByShortName(eventName); - if (event.isEmpty()) { - return "redirect:/"; - } - - Optional ticketReservation = ticketReservationManager.findById(reservationId); - if (ticketReservation.isEmpty()) { - return "redirect:/event/" + eventName + "/"; - } - - Locale locale = RequestContextUtils.getLocale(request); - ticketReservationManager.sendConfirmationEmail(event.get(), ticketReservation.orElseThrow(IllegalStateException::new), locale); - return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/success?confirmation-email-sent=true"; - } - - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/ticket/{ticketIdentifier}/assign", method = RequestMethod.POST) - public String assignTicketToPerson(@PathVariable("eventName") String eventName, - @PathVariable("reservationId") String reservationId, - @PathVariable("ticketIdentifier") String ticketIdentifier, - UpdateTicketOwnerForm updateTicketOwner, - BindingResult bindingResult, - HttpServletRequest request, - Model model) { - - Optional> result = ticketHelper.assignTicket(eventName, ticketIdentifier, updateTicketOwner, Optional.of(bindingResult), request, model); - return result.map(t -> "redirect:/event/"+t.getMiddle().getShortName()+"/reservation/"+t.getRight().getTicketsReservationId()+"/success").orElse("redirect:/"); - } - - private boolean isExpressCheckoutEnabled(Event event, OrderSummary orderSummary) { - return orderSummary.getTicketAmount() == 1 && ticketFieldRepository.countRequiredAdditionalFieldsForEvent(event.getId()) == 0; - } - - private Optional redirectIfNotValid(boolean backFromOverview, - boolean cancelReservation, - String eventName, - String reservationId, - HttpServletRequest request, - Optional eventOptional) { - - if (eventOptional.isEmpty()) { - return Optional.of("redirect:/"); - } - - Optional ticketReservation = ticketReservationManager.findById(reservationId); - if (ticketReservation.isEmpty() || ticketReservation.get().getStatus() != TicketReservationStatus.PENDING) { - return Optional.of(redirectReservation(ticketReservation, eventName, reservationId)); - } - - if(backFromOverview) { - ticketReservationRepository.updateValidationStatus(reservationId, false); - return Optional.of("redirect:/event/" + eventName + "/reservation/" + reservationId); - } - - if (cancelReservation) { - ticketReservationManager.cancelPendingReservation(reservationId, false, null); //FIXME - SessionUtil.cleanupSession(request); - return Optional.of("redirect:/event/" + eventName + "/"); - } - return Optional.empty(); - } - - private boolean isEUCountry(String countryCode) { - return configurationManager.getRequiredValue(getSystemConfiguration(ConfigurationKeys.EU_COUNTRIES_LIST)).contains(countryCode); - } - - - private static PriceContainer.VatStatus determineVatStatus(PriceContainer.VatStatus current, boolean isVatExempt) { - if(!isVatExempt) { - return current; - } - return current == NOT_INCLUDED ? NOT_INCLUDED_EXEMPT : INCLUDED_EXEMPT; - } -} diff --git a/src/main/java/alfio/controller/TicketController.java b/src/main/java/alfio/controller/TicketController.java deleted file mode 100644 index b92f3dac1e..0000000000 --- a/src/main/java/alfio/controller/TicketController.java +++ /dev/null @@ -1,249 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller; - -import alfio.controller.api.support.TicketHelper; -import alfio.controller.support.TemplateProcessor; -import alfio.controller.support.TicketDecorator; -import alfio.manager.*; -import alfio.manager.system.ConfigurationManager; -import alfio.model.Event; -import alfio.model.Ticket; -import alfio.model.TicketCategory; -import alfio.model.TicketReservation; -import alfio.model.system.Configuration; -import alfio.model.transaction.PaymentProxy; -import alfio.model.user.Organization; -import alfio.repository.TicketCategoryRepository; -import alfio.repository.user.OrganizationRepository; -import alfio.util.ImageUtil; -import alfio.util.LocaleUtil; -import alfio.util.TemplateManager; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Locale; -import java.util.Optional; - -import static alfio.model.system.ConfigurationKeys.ALLOW_FREE_TICKETS_CANCELLATION; -import static alfio.model.system.ConfigurationKeys.ENABLE_TICKET_TRANSFER; - -@Controller -@RequiredArgsConstructor -public class TicketController { - - private final OrganizationRepository organizationRepository; - private final TicketReservationManager ticketReservationManager; - private final TicketCategoryRepository ticketCategoryRepository; - private final TemplateManager templateManager; - private final NotificationManager notificationManager; - private final EventManager eventManager; - private final ConfigurationManager configurationManager; - private final FileUploadManager fileUploadManager; - private final TicketHelper ticketHelper; - private final ExtensionManager extensionManager; - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/{ticketIdentifier}", method = RequestMethod.GET) - public String showTicketOLD(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, - @PathVariable("ticketIdentifier") String ticketIdentifier) { - return "redirect:/event/"+eventName+"/ticket/"+ticketIdentifier; - } - - @RequestMapping(value = "/event/{eventName}/ticket/{ticketIdentifier}", method = RequestMethod.GET) - public String showTicket(@PathVariable("eventName") String eventName, - @PathVariable("ticketIdentifier") String ticketIdentifier, - @RequestParam(value = "ticket-email-sent", required = false, defaultValue = "false") boolean ticketEmailSent, - Locale locale, - Model model) { - return internalShowTicket(eventName, ticketIdentifier, ticketEmailSent, model, "success", locale); - - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/ticket/{ticketIdentifier}/view", method = RequestMethod.GET) - public String showTicketFromTicketDetailOLD(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, - @PathVariable("ticketIdentifier") String ticketIdentifier) { - return "redirect:/event/"+eventName+"/ticket/"+ticketIdentifier+"/view"; - } - - @RequestMapping(value = "/event/{eventName}/ticket/{ticketIdentifier}/view", method = RequestMethod.GET) - public String showTicketFromTicketDetail(@PathVariable("eventName") String eventName, - @PathVariable("ticketIdentifier") String ticketIdentifier, - @RequestParam(value = "ticket-email-sent", required = false, defaultValue = "false") boolean ticketEmailSent, - Model model, Locale locale) { - return internalShowTicket(eventName, ticketIdentifier, ticketEmailSent, model, "ticket/"+ticketIdentifier+"/update", locale); - } - - @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/ticket/{ticketIdentifier}/update", method = RequestMethod.GET) - public String showTicketForUpdateOLD(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, - @PathVariable("ticketIdentifier") String ticketIdentifier) { - return "redirect:/event/"+eventName+"/ticket/"+ticketIdentifier+"/update"; - } - @RequestMapping(value = "/event/{eventName}/ticket/{ticketIdentifier}/update", method = RequestMethod.GET) - public String showTicketForUpdate(@PathVariable("eventName") String eventName, - @PathVariable("ticketIdentifier") String ticketIdentifier, Model model, Locale locale) { - - Optional> oData = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier); - if(oData.isEmpty()) { - return "redirect:/event/" + eventName; - } - Triple data = oData.get(); - Event event = data.getLeft(); - TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(data.getRight().getCategoryId(), event.getId()); - Organization organization = organizationRepository.getById(event.getOrganizationId()); - boolean enableFreeCancellation = configurationManager.getBooleanConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ticketCategory.getId(), ALLOW_FREE_TICKETS_CANCELLATION), false); - Ticket ticket = data.getRight(); - model.addAttribute("ticketAndCategory", Pair.of(eventManager.getTicketCategoryById(ticket.getCategoryId(), event.getId()), new TicketDecorator(ticket, enableFreeCancellation, eventManager.checkTicketCancellationPrerequisites().apply(ticket), "ticket/"+ticket.getUuid()+"/view", ticketHelper.findTicketFieldConfigurationAndValue(ticket), true, "")))// - .addAttribute("reservation", data.getMiddle())// - .addAttribute("reservationId", ticketReservationManager.getShortReservationID(event, data.getMiddle())) - .addAttribute("event", event)// - .addAttribute("ticketCategory", ticketCategory)// - .addAttribute("countries", TicketHelper.getLocalizedCountries(locale)) - .addAttribute("organization", organization)// - .addAttribute("pageTitle", "show-ticket.header.title") - .addAttribute("useFirstAndLastName", event.mustUseFirstAndLastName()) - .addAttribute("transferEnabled", configurationManager.getBooleanConfigValue(Configuration.from(event).apply(ENABLE_TICKET_TRANSFER), true) && !ticket.getLockedAssignment()); - - return "/event/update-ticket"; - } - - - @RequestMapping(value = "/event/{eventName}/ticket/{ticketIdentifier}/send-ticket-by-email", method = RequestMethod.POST) - @ResponseBody - public String sendTicketByEmail(@PathVariable("eventName") String eventName, - @PathVariable("ticketIdentifier") String ticketIdentifier, - HttpServletRequest request) { - - Optional> oData = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier); - if(oData.isEmpty()) { - return "redirect:/event/" + eventName; - } - internalSendTicketByEmail(request, oData.get()); - return "OK"; - } - - private Ticket internalSendTicketByEmail(HttpServletRequest request, Triple data) { - Ticket ticket = data.getRight(); - Event event = data.getLeft(); - Locale locale = LocaleUtil.getTicketLanguage(ticket, request); - - TicketReservation reservation = data.getMiddle(); - Organization organization = organizationRepository.getById(event.getOrganizationId()); - TicketCategory category = ticketCategoryRepository.getById(ticket.getCategoryId()); - notificationManager.sendTicketByEmail(ticket, - event, locale, TemplateProcessor.buildPartialEmail(event, organization, reservation, category, templateManager, ticketReservationManager.ticketUpdateUrl(event, ticket.getUuid()), request), - reservation, ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId())); - return ticket; - } - - @RequestMapping(value = "/event/{eventName}/ticket/{ticketIdentifier}/download-ticket", method = RequestMethod.GET) - public void generateTicketPdf(@PathVariable("eventName") String eventName, - @PathVariable("ticketIdentifier") String ticketIdentifier, HttpServletRequest request, HttpServletResponse response) throws IOException { - - Optional> oData = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier); - if(oData.isEmpty()) { - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - Triple data = oData.get(); - - Ticket ticket = data.getRight(); - Event event = data.getLeft(); - TicketReservation ticketReservation = data.getMiddle(); - - response.setContentType("application/pdf"); - response.addHeader("Content-Disposition", "attachment; filename=ticket-" + ticketIdentifier + ".pdf"); - try (OutputStream os = response.getOutputStream()) { - TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId()); - Organization organization = organizationRepository.getById(event.getOrganizationId()); - String reservationID = ticketReservationManager.getShortReservationID(event, ticketReservation); - TemplateProcessor.renderPDFTicket(LocaleUtil.getTicketLanguage(ticket, request), event, ticketReservation, - ticket, ticketCategory, organization, - templateManager, fileUploadManager, - reservationID, os, ticketHelper.buildRetrieveFieldValuesFunction(), extensionManager); - } - } - - @RequestMapping(value = "/event/{eventName}/ticket/{ticketIdentifier}/code.png", method = RequestMethod.GET) - public void generateTicketCode(@PathVariable("eventName") String eventName, - @PathVariable("ticketIdentifier") String ticketIdentifier, HttpServletResponse response) throws IOException { - - Optional> oData = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier); - if(oData.isEmpty()) { - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - Triple data = oData.get(); - - Event event = data.getLeft(); - Ticket ticket = data.getRight(); - - String qrCodeText = ticket.ticketCode(event.getPrivateKey()); - - response.setContentType("image/png"); - response.getOutputStream().write(ImageUtil.createQRCode(qrCodeText)); - - } - - @RequestMapping(value = "/event/{eventName}/cancel-ticket", method = RequestMethod.POST) - public String cancelTicket(@PathVariable("eventName") String eventName, - @RequestParam("ticketId") String ticketIdentifier) { - Optional> oData = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier); - oData.ifPresent(triple -> ticketReservationManager.releaseTicket(triple.getLeft(), triple.getMiddle(), triple.getRight())); - return "redirect:/event/" + eventName; - } - - private String internalShowTicket(String eventName, String ticketIdentifier, boolean ticketEmailSent, Model model, String backSuffix, Locale locale) { - Optional> oData = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier); - if(oData.isEmpty()) { - return "redirect:/event/" + eventName; - } - Triple data = oData.get(); - - - TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(data.getRight().getCategoryId(), data.getLeft().getId()); - Organization organization = organizationRepository.getById(data.getLeft().getOrganizationId()); - Event event = data.getLeft(); - - TicketReservation reservation = data.getMiddle(); - model.addAttribute("ticket", data.getRight())// - .addAttribute("reservation", reservation)// - .addAttribute("event", event)// - .addAttribute("ticketCategory", ticketCategory)// - .addAttribute("organization", organization)// - .addAttribute("ticketEmailSent", ticketEmailSent) - .addAttribute("reservationId", ticketReservationManager.getShortReservationID(event, reservation)) - .addAttribute("deskPaymentRequired", Optional.ofNullable(reservation.getPaymentMethod()).orElse(PaymentProxy.STRIPE).isDeskPaymentRequired()) - .addAttribute("backSuffix", backSuffix) - .addAttribute("userLanguage", locale.getLanguage()) - .addAttribute("pageTitle", "show-ticket.header.title") - .addAttribute("useFirstAndLastName", event.mustUseFirstAndLastName()) - .addAttribute("validityStart", Optional.ofNullable(ticketCategory.getTicketValidityStart(event.getZoneId())).orElse(event.getBegin())) - .addAttribute("validityEnd", Optional.ofNullable(ticketCategory.getTicketValidityEnd(event.getZoneId())).orElse(event.getEnd())) - .addAttribute("ticketIdParam", "ticketId="+ticketIdentifier); - - return "/event/show-ticket"; - } -} diff --git a/src/main/java/alfio/controller/WaitingQueueController.java b/src/main/java/alfio/controller/WaitingQueueController.java deleted file mode 100644 index 0ae9994632..0000000000 --- a/src/main/java/alfio/controller/WaitingQueueController.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller; - -import alfio.controller.form.WaitingQueueSubscriptionForm; -import alfio.manager.WaitingQueueManager; -import alfio.model.Event; -import alfio.repository.EventRepository; -import alfio.util.Validator; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; - -@Controller -public class WaitingQueueController { - - private final WaitingQueueManager waitingQueueManager; - private final EventRepository eventRepository; - - @Autowired - public WaitingQueueController(WaitingQueueManager waitingQueueManager, - EventRepository eventRepository) { - this.waitingQueueManager = waitingQueueManager; - this.eventRepository = eventRepository; - } - - @RequestMapping(value = "/event/{eventName}/waiting-queue/subscribe", method = RequestMethod.POST) - public String subscribe(@ModelAttribute WaitingQueueSubscriptionForm subscription, BindingResult bindingResult, Model model, @PathVariable("eventName") String eventName, RedirectAttributes redirectAttributes) { - Event event = eventRepository.findOptionalByShortName(eventName).orElseThrow(IllegalArgumentException::new); - Validator.validateWaitingQueueSubscription(subscription, bindingResult, event).ifSuccess(() -> { - if(waitingQueueManager.subscribe(event, subscription.toCustomerName(event), subscription.getEmail(), subscription.getSelectedCategory(), subscription.getUserLanguage())) { - redirectAttributes.addFlashAttribute("subscriptionComplete", true); - } else { - redirectAttributes.addFlashAttribute("subscriptionError", true); - } - }); - return "redirect:/event/"+eventName+"/"; - } - -} diff --git a/src/main/java/alfio/controller/api/ReservationApiController.java b/src/main/java/alfio/controller/api/ReservationApiController.java deleted file mode 100644 index 6b0084f778..0000000000 --- a/src/main/java/alfio/controller/api/ReservationApiController.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller.api; - -import alfio.controller.api.support.TicketHelper; -import alfio.controller.form.UpdateTicketOwnerForm; -import alfio.manager.TicketReservationManager; -import alfio.manager.i18n.I18nManager; -import alfio.model.ContentLanguage; -import alfio.model.Event; -import alfio.model.Ticket; -import alfio.model.result.ValidationResult; -import alfio.repository.EventRepository; -import alfio.repository.TicketReservationRepository; -import alfio.util.TemplateManager; -import lombok.AllArgsConstructor; -import org.apache.commons.lang3.tuple.Triple; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.support.RequestContextUtils; - -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -@RestController -@AllArgsConstructor -public class ReservationApiController { - - private final TicketHelper ticketHelper; - private final TemplateManager templateManager; - private final I18nManager i18nManager; - - - @RequestMapping(value = "/event/{eventName}/ticket/{ticketIdentifier}/assign", method = RequestMethod.POST, headers = "X-Requested-With=XMLHttpRequest") - public Map assignTicketToPerson(@PathVariable("eventName") String eventName, - @PathVariable("ticketIdentifier") String ticketIdentifier, - @RequestParam(value = "single-ticket", required = false, defaultValue = "false") boolean singleTicket, - UpdateTicketOwnerForm updateTicketOwner, - BindingResult bindingResult, - HttpServletRequest request, - Model model, - Authentication authentication) { - - Optional userDetails = Optional.ofNullable(authentication) - .map(Authentication::getPrincipal) - .filter(UserDetails.class::isInstance) - .map(UserDetails.class::cast); - - Optional> assignmentResult = ticketHelper.assignTicket(eventName, ticketIdentifier, updateTicketOwner, Optional.of(bindingResult), request, t -> { - Locale requestLocale = RequestContextUtils.getLocale(request); - model.addAttribute("ticketFieldConfiguration", ticketHelper.findTicketFieldConfigurationAndValue(t.getRight())); - model.addAttribute("value", t.getRight()); - model.addAttribute("validationResult", t.getLeft()); - model.addAttribute("countries", TicketHelper.getLocalizedCountries(requestLocale)); - model.addAttribute("event", t.getMiddle()); - model.addAttribute("useFirstAndLastName", t.getMiddle().mustUseFirstAndLastName()); - model.addAttribute("availableLanguages", i18nManager.getEventLanguages(eventName).stream() - .map(ContentLanguage.toLanguage(requestLocale)).collect(Collectors.toList())); - String uuid = t.getRight().getUuid(); - model.addAttribute("urlSuffix", singleTicket ? "ticket/"+uuid+"/view": uuid); - model.addAttribute("elementNamePrefix", ""); - }, userDetails, false); - Map result = new HashMap<>(); - - Optional validationResult = assignmentResult.map(Triple::getLeft); - if(validationResult.isPresent() && validationResult.get().isSuccess()) { - result.put("partial", templateManager.renderServletContextResource("/WEB-INF/templates/event/assign-ticket-result.ms", - assignmentResult.get().getMiddle(),//<- ugly, but will be removed - model.asMap(), request, TemplateManager.TemplateOutput.HTML)); - } - result.put("validationResult", validationResult.orElse(ValidationResult.failed(new ValidationResult.ErrorDescriptor("fullName", "error.fullname")))); - return result; - } -} diff --git a/src/main/java/alfio/controller/api/WaitingQueueApiController.java b/src/main/java/alfio/controller/api/WaitingQueueApiController.java deleted file mode 100644 index 4c43fb8042..0000000000 --- a/src/main/java/alfio/controller/api/WaitingQueueApiController.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller.api; - -import alfio.controller.form.WaitingQueueSubscriptionForm; -import alfio.manager.WaitingQueueManager; -import alfio.model.Event; -import alfio.model.result.ValidationResult; -import alfio.repository.EventRepository; -import alfio.util.TemplateManager; -import alfio.util.Validator; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static alfio.util.TemplateManager.TemplateOutput.HTML; - -@RestController -public class WaitingQueueApiController { - - private final EventRepository eventRepository; - private final WaitingQueueManager waitingQueueManager; - private final TemplateManager templateManager; - - @Autowired - public WaitingQueueApiController(EventRepository eventRepository, - WaitingQueueManager waitingQueueManager, - TemplateManager templateManager) { - this.eventRepository = eventRepository; - this.waitingQueueManager = waitingQueueManager; - this.templateManager = templateManager; - } - - @RequestMapping(value = "/event/{eventName}/waiting-queue/subscribe", method = RequestMethod.POST, headers = "X-Requested-With=XMLHttpRequest") - public Map subscribe(WaitingQueueSubscriptionForm subscription, - BindingResult bindingResult, - Model model, - @PathVariable("eventName") String eventName, - HttpServletRequest request) { - - Optional optional = eventRepository.findOptionalByShortName(eventName); - Map result = new HashMap<>(); - if(optional.isEmpty()) { - bindingResult.reject(""); - result.put("validationResult", ValidationResult.failed(new ValidationResult.ErrorDescriptor("shortName", "error.shortName"))); - return result; - } - - Event event = optional.get(); - - ValidationResult validationResult = Validator.validateWaitingQueueSubscription(subscription, bindingResult, event); - if(validationResult.isSuccess()) { - model.addAttribute("error", !waitingQueueManager.subscribe(event, subscription.toCustomerName(event), subscription.getEmail(), subscription.getSelectedCategory(), subscription.getUserLanguage())); - result.put("partial", templateManager.renderServletContextResource("/WEB-INF/templates/event/waiting-queue-subscription-result.ms", event, model.asMap(), request, HTML)); - } - result.put("validationResult", validationResult); - return result; - } -} diff --git a/src/main/java/alfio/controller/decorator/DecoratorUtil.java b/src/main/java/alfio/controller/decorator/DecoratorUtil.java deleted file mode 100644 index 7b678b5f45..0000000000 --- a/src/main/java/alfio/controller/decorator/DecoratorUtil.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller.decorator; - -import alfio.model.PromoCodeDiscount; -import alfio.util.MonetaryUtil; - -import java.math.BigDecimal; -import java.util.stream.IntStream; - -import static java.lang.Math.max; -import static java.lang.Math.min; - -class DecoratorUtil { - static int[] generateRangeOfTicketQuantity(int maxTickets, int availableTickets) { - final int maximumSaleableTickets = max(0, min(maxTickets, availableTickets)); - return IntStream.rangeClosed(0, maximumSaleableTickets).toArray(); - } - - static int calcDiscount(PromoCodeDiscount d, int finalPriceInCents) { - int discount; - if(d.getFixedAmount()) { - discount = d.getDiscountAmount(); - } else { - discount = MonetaryUtil.calcPercentage(finalPriceInCents, new BigDecimal(d.getDiscountAmount())); - } - return finalPriceInCents - discount; - } -} diff --git a/src/main/java/alfio/controller/decorator/SaleableAdditionalService.java b/src/main/java/alfio/controller/decorator/SaleableAdditionalService.java index 1bc5691a01..1a5b022002 100644 --- a/src/main/java/alfio/controller/decorator/SaleableAdditionalService.java +++ b/src/main/java/alfio/controller/decorator/SaleableAdditionalService.java @@ -119,10 +119,6 @@ public boolean getSupportsDiscount() { && promoCodeDiscount != null && promoCodeDiscount.getCodeType() == PromoCodeDiscount.CodeType.DISCOUNT; } - public boolean getUserDefinedPrice() { - return !isFixPrice(); - } - public String getDiscountedPrice() { return getFinalPrice().toPlainString(); } @@ -141,24 +137,6 @@ public boolean getVatIncluded() { } } - public boolean getMandatoryOneForTicket() { - return getSupplementPolicy() == AdditionalService.SupplementPolicy.MANDATORY_ONE_FOR_TICKET; - } - - public boolean getUnlimitedAmount() { - return getSupplementPolicy() == AdditionalService.SupplementPolicy.OPTIONAL_UNLIMITED_AMOUNT; - } - - public boolean getLimitedAmount() { - return getSupplementPolicy() == null || - getSupplementPolicy() == AdditionalService.SupplementPolicy.OPTIONAL_MAX_AMOUNT_PER_RESERVATION || - getSupplementPolicy() == AdditionalService.SupplementPolicy.OPTIONAL_MAX_AMOUNT_PER_TICKET; - } - - public boolean getMaxAmountPerTicket() { - return getSupplementPolicy() == AdditionalService.SupplementPolicy.OPTIONAL_MAX_AMOUNT_PER_TICKET; - } - public BigDecimal getVatPercentage() { AdditionalService.VatType vatType = getVatType(); if(vatType == AdditionalService.VatType.INHERITED) { @@ -171,17 +149,6 @@ public boolean getVatApplies() { return getVatType() != AdditionalService.VatType.NONE; } - public int[] getAmountOfTickets() { - return DecoratorUtil.generateRangeOfTicketQuantity(additionalService.getMaxQtyPerOrder(), 100); - } - - public boolean getSoldOut() { - return false; - } - - private boolean getAccessRestricted() { - return false; - } public String getCurrency() { return event.getCurrency(); diff --git a/src/main/java/alfio/controller/decorator/SaleableTicketCategory.java b/src/main/java/alfio/controller/decorator/SaleableTicketCategory.java index cdaa3025f9..417b5e1210 100644 --- a/src/main/java/alfio/controller/decorator/SaleableTicketCategory.java +++ b/src/main/java/alfio/controller/decorator/SaleableTicketCategory.java @@ -85,19 +85,11 @@ public boolean getSaleInFuture() { public boolean getAccessRestricted() { return isAccessRestricted(); } - - public boolean getSouldOut() { - return soldOut; - } public boolean getSouldOutOrLimitReached() { return soldOut || (promoCodeDiscount != null && maxTickets == 0); } - public String getFormattedExpiration() { - return getExpiration(zoneId).format(DateTimeFormatter.ISO_DATE_TIME); - } - public ZonedDateTime getZonedExpiration() { return getExpiration(zoneId); } @@ -134,11 +126,6 @@ public String getFormattedFinalPrice() { return getFinalPriceToDisplay(getFinalPrice().add(getAppliedDiscount()), getVAT(), getVatStatus()).toString(); } - public int[] getAmountOfTickets() { - return DecoratorUtil.generateRangeOfTicketQuantity(maxTickets, availableTickets); - } - - public int getMaxTicketsAfterConfiguration() { return maxTickets; } diff --git a/src/main/java/alfio/controller/support/SessionUtil.java b/src/main/java/alfio/controller/support/SessionUtil.java index 07bb2fedbf..1980c12da6 100644 --- a/src/main/java/alfio/controller/support/SessionUtil.java +++ b/src/main/java/alfio/controller/support/SessionUtil.java @@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.Optional; -import java.util.UUID; public final class SessionUtil { @@ -35,17 +34,6 @@ public final class SessionUtil { private SessionUtil() {} - public static void saveSpecialPriceCode(String specialPriceCode, HttpServletRequest request) { - if(StringUtils.isNotEmpty(specialPriceCode)) { - request.getSession().setAttribute(SPECIAL_PRICE_CODE_SESSION_ID, UUID.randomUUID().toString()); - request.getSession().setAttribute(SPECIAL_PRICE_CODE, specialPriceCode); - } - } - - public static void savePromotionCodeDiscount(String promoCodeDiscount, HttpServletRequest request) { - request.getSession().setAttribute(PROMOTIONAL_CODE_DISCOUNT, promoCodeDiscount); - } - public static void saveSpecialPriceCodeOnRequestAttr(String specialPriceCode, HttpServletRequest request) { if(StringUtils.isNotEmpty(specialPriceCode)) { request.setAttribute(SPECIAL_PRICE_CODE, specialPriceCode); diff --git a/src/main/java/alfio/controller/support/TicketDecorator.java b/src/main/java/alfio/controller/support/TicketDecorator.java deleted file mode 100644 index bb63ac9085..0000000000 --- a/src/main/java/alfio/controller/support/TicketDecorator.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller.support; - -import alfio.model.Ticket; -import alfio.model.TicketFieldConfigurationDescriptionAndValue; -import lombok.experimental.Delegate; - -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -public class TicketDecorator { - - public static final Function EMPTY_PREFIX_GENERATOR = (t) -> ""; - - @Delegate - private final Ticket ticket; - private final boolean freeCancellationEnabled; - private final boolean conditionsMet; - private final String urlSuffix; - private final List ticketFieldConfiguration; - private final boolean forceDisplayAssignForm; - private final String elementNamePrefix; - - private TicketDecorator(Ticket ticket, - boolean freeCancellationEnabled, - boolean conditionsMet, - List ticketFieldConfiguration, - boolean forceDisplayAssignForm, - String elementNamePrefix) { - this(ticket, freeCancellationEnabled, conditionsMet, ticket.getUuid(), ticketFieldConfiguration, forceDisplayAssignForm, elementNamePrefix); - } - - public TicketDecorator(Ticket ticket, - boolean freeCancellationEnabled, - boolean conditionsMet, - String urlSuffix, - List ticketFieldConfiguration, - boolean forceDisplayAssignForm, - String elementNamePrefix) { - this.ticket = ticket; - this.freeCancellationEnabled = freeCancellationEnabled; - this.conditionsMet = conditionsMet; - this.urlSuffix = urlSuffix; - this.ticketFieldConfiguration = ticketFieldConfiguration; - this.forceDisplayAssignForm = forceDisplayAssignForm; - this.elementNamePrefix = elementNamePrefix; - } - - public String getUrlSuffix() { - return urlSuffix; - } - - public boolean hasBeenPaid() { - return !isFree(); - } - - public boolean isFree() { - return getFinalPriceCts() == 0; - } - - public boolean getCancellationEnabled() { - return isFree() && freeCancellationEnabled && conditionsMet; - } - - public List getTicketFieldConfiguration() { - return ticketFieldConfiguration; - } - - public List getTicketFieldConfigurationBeforeStandard() { - return ticketFieldConfiguration.stream().filter(TicketFieldConfigurationDescriptionAndValue::isBeforeStandardFields).collect(Collectors.toList()); - } - - public List getTicketFieldConfigurationAfterStandard() { - return ticketFieldConfiguration.stream().filter(tv -> !tv.isBeforeStandardFields()).collect(Collectors.toList()); - } - - public String getElementNamePrefix() { - return elementNamePrefix; - } - - public boolean getDisplayAssignForm() { - return !getAssigned() || forceDisplayAssignForm; - } - - public static List decorate(List tickets, - boolean freeCancellationEnabled, - Function categoryEvaluator, - Function> fieldsLoader, - boolean forceDisplayAssignForm, - Function elementNamePrefixGenerator) { - return tickets.stream() - .map(t -> new TicketDecorator(t, freeCancellationEnabled, - categoryEvaluator.apply(t), fieldsLoader.apply(t), - forceDisplayAssignForm, elementNamePrefixGenerator.apply(t))).collect(Collectors.toList()); - } -} diff --git a/src/main/webapp/WEB-INF/templates/event/404-not-found.ms b/src/main/webapp/WEB-INF/templates/event/404-not-found.ms deleted file mode 100644 index 153fcc7f3a..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/404-not-found.ms +++ /dev/null @@ -1,14 +0,0 @@ -{{>/event/page-top}} - -
-
- -
-
-

Oops...

-

{{#i18n}}not-found{{/i18n}}

- {{#i18n}}to-home{{/i18n}} -
-
- -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/500-internal-server-error.ms b/src/main/webapp/WEB-INF/templates/event/500-internal-server-error.ms deleted file mode 100644 index eda0c3c1c4..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/500-internal-server-error.ms +++ /dev/null @@ -1,14 +0,0 @@ -{{>/event/page-top}} - -
-
- -
-
-

D'oh!

-

{{#i18n}}server-error{{/i18n}}

- {{#i18n}}to-home{{/i18n}} -
-
- -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/additional-field.ms b/src/main/webapp/WEB-INF/templates/event/additional-field.ms deleted file mode 100644 index 0f1094b777..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/additional-field.ms +++ /dev/null @@ -1,43 +0,0 @@ -
- -
- {{#inputField}} - - {{/inputField}} - {{#euVat}} - - {{/euVat}} - {{#textareaField}} - - {{/textareaField}} - {{#countryField}} - - {{/countryField}} - {{#selectField}} - - {{/selectField}} -
-
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/additional-service.ms b/src/main/webapp/WEB-INF/templates/event/additional-service.ms deleted file mode 100644 index 06f8238854..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/additional-service.ms +++ /dev/null @@ -1,90 +0,0 @@ -
  • -
    -
    - - {{^expired}} -
    - {{#saleInFuture}} - {{#i18n}}show-event.sales-not-started [{{#format-date}}{{zonedInception}} dd.MM.yyyy HH:mm locale:{{#i18n}}locale{{/i18n}}{{/format-date}}]{{/i18n}} - {{/saleInFuture}} - {{^saleInFuture}} - {{#i18n}}show-event.sales-end [{{#format-date}}{{zonedExpiration}} dd.MM.yyyy HH:mm locale:{{#i18n}}locale{{/i18n}}{{/format-date}}]{{/i18n}} - {{/saleInFuture}} -
    - {{/expired}} - {{#expired}} -
    - {{#i18n}}show-event.sales-ended [{{#format-date}}{{zonedExpiration}} dd.MM.yyyy HH:mm locale:{{#i18n}}locale{{/i18n}}{{/format-date}}]{{/i18n}} -
    - {{/expired}} -
    - {{#commonmark}}{{description}}{{/commonmark}} -
    -
    -
    - -
    -
    - {{#saleable}} - {{#fixPrice}} - {{#mandatoryOneForTicket}} - {{#i18n}}show-event.mandatoryOneForTicket{{/i18n}} - {{/mandatoryOneForTicket}} - {{#unlimitedAmount}} - - - {{/unlimitedAmount}} - {{#limitedAmount}} - - - {{/limitedAmount}} - {{/fixPrice}} - {{^fixPrice}} - - - {{/fixPrice}} - {{/saleable}} - {{^saleable}} - - {{/saleable}} -
    -
    -
  • \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/assign-ticket-form.ms b/src/main/webapp/WEB-INF/templates/event/assign-ticket-form.ms deleted file mode 100644 index e255131bd2..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/assign-ticket-form.ms +++ /dev/null @@ -1,61 +0,0 @@ -{{#assigned}} -
    -

    {{#i18n}}reservation-page-complete.assigned-to [{{fullName}}] [{{email}}]{{/i18n}} {{#cancellationEnabled}} {{#i18n}}reservation-page-complete.release-button.text{{/i18n}}{{/cancellationEnabled}}

    - {{#cancellationEnabled}} - - {{/cancellationEnabled}} - - - -
    -{{/assigned}} -{{#validationResult}} - {{^isSuccess}} -
    {{#i18n}}reservation-page-complete.please-check-input-fields{{/i18n}}
    - {{/isSuccess}} -{{/validationResult}} -
    - -{{> /event/attendee-fields }} - -
    {{#i18n}}error.generic{{/i18n}}
    - -
    -
    - - - {{#assigned}} - - {{/assigned}} - - - -
    -
    -
    -
    {{#i18n}}reservation-page-complete.please-check-input-fields{{/i18n}}
    -
    diff --git a/src/main/webapp/WEB-INF/templates/event/assign-ticket-result.ms b/src/main/webapp/WEB-INF/templates/event/assign-ticket-result.ms deleted file mode 100644 index b129feb220..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/assign-ticket-result.ms +++ /dev/null @@ -1,5 +0,0 @@ -{{#value}} -
    - {{> /WEB-INF/templates/event/assign-ticket-form.ms }} -
    -{{/value}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/attendee-fields.ms b/src/main/webapp/WEB-INF/templates/event/attendee-fields.ms deleted file mode 100644 index f0e74e2fb5..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/attendee-fields.ms +++ /dev/null @@ -1,77 +0,0 @@ -{{#ticketFieldConfigurationBeforeStandard}} - {{#fields}} - {{> /event/additional-field }} - {{/fields}} -{{/ticketFieldConfigurationBeforeStandard}} -{{#useFirstAndLastName}} -
    - -
    - {{^lockedAssignment}} - - {{/lockedAssignment}} - {{#lockedAssignment}} -

    {{firstName}}

    - {{/lockedAssignment}} -
    -
    -
    - -
    - {{^lockedAssignment}} - - {{/lockedAssignment}} - {{#lockedAssignment}} -

    {{lastName}}

    - {{/lockedAssignment}} -
    -
    -{{/useFirstAndLastName}} -{{^useFirstAndLastName}} -
    - -
    - {{^lockedAssignment}} - - {{/lockedAssignment}} - {{#lockedAssignment}} -

    {{fullName}}

    - {{/lockedAssignment}} -
    -
    -{{/useFirstAndLastName}} -
    - -
    - {{^lockedAssignment}} - - {{/lockedAssignment}} - {{#lockedAssignment}} -

    {{email}}

    - {{/lockedAssignment}} -
    -
    -
    - {{#ticketFieldConfigurationAfterStandard}} - {{#fields}} - {{> /event/additional-field }} - {{/fields}} - {{/ticketFieldConfigurationAfterStandard}} - {{#showAvailableLanguagesInPageTop}} -
    - -
    - -
    -
    - {{/showAvailableLanguagesInPageTop}} - {{^showAvailableLanguagesInPageTop}} -
    - {{/showAvailableLanguagesInPageTop}} - -
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/event-details.ms b/src/main/webapp/WEB-INF/templates/event/event-details.ms deleted file mode 100644 index 27d11fc866..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/event-details.ms +++ /dev/null @@ -1,37 +0,0 @@ -

    {{#i18n}}ticket.event-info{{/i18n}}

    -
    {{#i18n}}show-event.by{{/i18n}} {{organization.name}} <{{organization.email}}>
    -
    -
      -
    • - - {{#event.sameDay}} - {{#format-date}}{{event.begin}} EEEE dd MMMM yyyy locale:{{#i18n}}locale{{/i18n}}{{/format-date}}
      -
    • -
    • - - {{#i18n}}event-days.single-day.hours - [{{#format-date}}{{validityStart}} HH:mm{{/format-date}}] - [{{#format-date}}{{validityEnd}} HH:mm{{/format-date}}] - {{/i18n}} - {{/event.sameDay}} - {{^event.sameDay}} - {{#i18n}}event-days.not-same-day - [{{#format-date}}{{validityStart}} EEEE dd MMMM yyyy locale:{{#i18n}}locale{{/i18n}}{{/format-date}}] - [{{#format-date}}{{validityStart}} HH:mm{{/format-date}}] - {{/i18n}} - - - {{#i18n}}event-days.not-same-day - [{{#format-date}}{{validityEnd}} EEEE dd MMMM yyyy locale:{{#i18n}}locale{{/i18n}}{{/format-date}}] - [{{#format-date}}{{validityEnd}} HH:mm{{/format-date}}] - {{/i18n}} - {{/event.sameDay}} - -
    • -
    • - {{#i18n}}show-event.add-to-calendar{{/i18n}} -
    • -
    • - {{event.location}} -
    • -
    -
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/event-list.ms b/src/main/webapp/WEB-INF/templates/event/event-list.ms deleted file mode 100644 index f0e5410783..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/event-list.ms +++ /dev/null @@ -1,50 +0,0 @@ -{{>/event/page-top}} - -

    {{#i18n}}event-list.title{{/i18n}}

    - - -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/header.ms b/src/main/webapp/WEB-INF/templates/event/header.ms deleted file mode 100644 index e27617fa0c..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/header.ms +++ /dev/null @@ -1,9 +0,0 @@ -
    -
    - {{>/event/output-event-image-tag}} -
    -
    -

    {{event.displayName}}

    -
    -
    -
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/invoice-fields.ms b/src/main/webapp/WEB-INF/templates/event/invoice-fields.ms deleted file mode 100644 index 48b105f32a..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/invoice-fields.ms +++ /dev/null @@ -1,163 +0,0 @@ - - -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    -
    - - - {{#field-has-error}}[billingAddressCompany]{{#i18n}}{{#field-error}}billingAddressCompany{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    -
    -
    -
    - - - {{#field-has-error}}[billingAddressLine1]{{#i18n}}{{#field-error}}billingAddressLine1{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - - {{#field-has-error}}[billingAddressZip]{{#i18n}}{{#field-error}}billingAddressZip{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    -
    - - - {{#field-has-error}}[billingAddressCity]{{#i18n}}{{#field-error}}billingAddressCity{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    -
    -
    -
    - - - {{#field-has-error}}[vatCountryCode]{{#i18n}}{{#field-error}}vatCountryCode{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    - {{#customerReferenceEnabled}} -
    -
    - - - {{#field-has-error}}[customerReference]{{#i18n}}{{#field-error}}customerReference{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    - {{/customerReferenceEnabled}} -
    -
    -
    -
    -
    - -
    -
    - -
    - {{#field-has-error}}[vatNr]{{#i18n}}{{#field-error}}vatNr{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    - {{^vatNumberStrictlyRequired}} -
    -
    -
    - -
    -
    -
    - {{/vatNumberStrictlyRequired}} -
    -
    -{{#enabledItalyEInvoicing}} -
    -
    -
    -
    - - -
    -
    -
    -
    - - {{#field-has-error}}[italyEInvoicingReferenceType]{{#i18n}}{{#field-error}}italyEInvoicingReferenceType{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    -
    - - - {{#field-has-error}}[italyEInvoicingReferenceAddresseeCode]{{#i18n}}{{#field-error}}italyEInvoicingReferenceAddresseeCode{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    -
    - - - {{#field-has-error}}[italyEInvoicingReferencePEC]{{#i18n}}{{#field-error}}italyEInvoicingReferencePEC{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    -
    - -
    -
    -
    -
    -{{/enabledItalyEInvoicing}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/output-event-image-tag.ms b/src/main/webapp/WEB-INF/templates/event/output-event-image-tag.ms deleted file mode 100644 index a7bc7f8d4c..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/output-event-image-tag.ms +++ /dev/null @@ -1,8 +0,0 @@ -{{#event.imageIsPresent}} - {{#event.fileBlobIdIsPresent}} - - {{/event.fileBlobIdIsPresent}} - {{^event.fileBlobIdIsPresent}} - - {{/event.fileBlobIdIsPresent}} -{{/event.imageIsPresent}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/overview.ms b/src/main/webapp/WEB-INF/templates/event/overview.ms deleted file mode 100644 index 9c9e7e465e..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/overview.ms +++ /dev/null @@ -1,225 +0,0 @@ -{{>/event/page-top}} - - - - - - - -{{>/event/header}} - -
    -
    -
    {{#i18n}}breadcrumb.step1{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step2{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step3{{#event.free}}.free{{/event.free}}{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step4{{/i18n}}
    -
    -
    - -{{#hasErrors}} - {{#error.globalErrors}} - - {{/error.globalErrors}} -{{/hasErrors}} - -
    - - - -
    - - - -
    -
    - - - - - - - - - - - {{#orderSummary.summary}} - - - - - - - {{/orderSummary.summary}} - - - {{^orderSummary.free}} - {{#orderSummary.displayVat}} - {{^event.vatIncluded}} - - {{/event.vatIncluded}} - {{/orderSummary.displayVat}} - {{/orderSummary.free}} - - - {{^orderSummary.free}} - {{#orderSummary.displayVat}} - {{#event.vatIncluded}} - - {{/event.vatIncluded}} - {{/orderSummary.displayVat}} - {{^orderSummary.displayVat}} - - {{/orderSummary.displayVat}} - {{/orderSummary.free}} - -
    {{#i18n}}reservation-page.category{{/i18n}}{{#i18n}}reservation-page.amount{{/i18n}}{{#i18n}}reservation-page.price{{/i18n}}{{#i18n}}reservation-page.subtotal{{/i18n}}
    {{name}}{{amount}}{{price}}{{subTotal}} {{event.currency}}
    {{#i18n}}reservation-page.vat [{{event.vat}}] [{{vatTranslation}}]{{/i18n}}{{orderSummary.totalVAT}} {{event.currency}}
    {{#i18n}}reservation-page.total{{/i18n}}{{orderSummary.totalPrice}} {{event.currency}}
    {{#i18n}}reservation-page.vat-included [{{event.vat}}] [{{vatTranslation}}]{{/i18n}}{{orderSummary.totalVAT}} {{event.currency}}
    {{#i18n}}invoice.vat-voided [{{vatTranslation}}]{{/i18n}}
    -
    -{{#displayInvoiceData}} -
    -
    - - -
    {{reservation.billingAddress}}{{#billingDetails.hasTaxId}} -{{#i18n}}invoice.vat [{{vatTranslation}}]{{/i18n}} {{billingDetails.taxId}}{{/billingDetails.hasTaxId}}
    - {{^billingDetails.invoicingAdditionalInfo.empty}} -
    {{billingDetails.invoicingAdditionalInfo.italianEInvoicing.fiscalCode}}
    -
    {{billingDetails.invoicingAdditionalInfo.italianEInvoicing.reference}}
    - {{/billingDetails.invoicingAdditionalInfo.empty}} -
    -
    -{{/displayInvoiceData}} -
    - - - -
    - - {{^orderSummary.free}} - - {{#multiplePaymentMethods}} -
    - {{#activePaymentMethods}} - - {{/activePaymentMethods}} -
    - {{/multiplePaymentMethods}} - {{^multiplePaymentMethods}} - {{#activePaymentMethods}} -

    - {{#is-payment-method}}[STRIPE] {{#i18n}}reservation-page.credit-card{{/i18n}}{{/is-payment-method}} - {{#is-payment-method}}[PAYPAL] {{#i18n}}reservation-page.paypal{{/i18n}}{{/is-payment-method}} - {{#is-payment-method}}[MOLLIE] {{#i18n}}reservation-page.mollie{{/i18n}}{{/is-payment-method}} - {{#is-payment-method}}[ON_SITE] {{#i18n}}reservation-page.on-site{{/i18n}}{{/is-payment-method}} - {{#is-payment-method}}[OFFLINE] {{#i18n}}reservation-page.offline{{/i18n}}{{/is-payment-method}} -

    - - {{/activePaymentMethods}} - {{/multiplePaymentMethods}} - {{#activePaymentMethods}} -
    - {{#is-payment-method}}[STRIPE]{{> /event/payment/stripe }}{{/is-payment-method}} - {{#is-payment-method}}[PAYPAL]{{> /event/payment/paypal }}{{/is-payment-method}} - {{#is-payment-method}}[MOLLIE]{{> /event/payment/mollie }}{{/is-payment-method}} - {{#is-payment-method}}[ON_SITE]{{> /event/payment/on-site }}{{/is-payment-method}} - {{#is-payment-method}}[OFFLINE]{{> /event/payment/offline }}{{/is-payment-method}} -
    - {{/activePaymentMethods}} - -
    - -
    - - {{/orderSummary.free}} - {{#orderSummary.free}} - - - {{/orderSummary.free}} - - - - {{#event.privacyPolicyLinkOrNull}} -
    - -
    - {{/event.privacyPolicyLinkOrNull}} - - - -
    - -
    - - {{#orderSummary.free}} - {{#captchaRequestedFreeOfCharge}} -
    -
    -
    -
    -
    - {{/captchaRequestedFreeOfCharge}} -
    -
    -
    -
    - {{/orderSummary.free}} - {{^orderSummary.free}} -
    -
    - -
    -
    -
    - {{/orderSummary.free}} - - -
    -{{#captchaRequestedForOffline}} - -{{/captchaRequestedForOffline}} -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/page-bottom.ms b/src/main/webapp/WEB-INF/templates/event/page-bottom.ms deleted file mode 100644 index 6a62cbee45..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/page-bottom.ms +++ /dev/null @@ -1,13 +0,0 @@ - - - - -{{#analyticsEnabled}} - - - - -{{/analyticsEnabled}} - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/page-top.ms b/src/main/webapp/WEB-INF/templates/event/page-top.ms deleted file mode 100644 index 22ff83007b..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/page-top.ms +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - {{#i18n}}{{pageTitle}}[{{event.displayName}}]{{/i18n}} - -{{#event.imageIsPresent}} - {{#event.fileBlobIdIsPresent}} - - {{/event.fileBlobIdIsPresent}} - {{^event.fileBlobIdIsPresent}} - - {{/event.fileBlobIdIsPresent}} -{{/event.imageIsPresent}} - - - - - -
    -
    -
    -
    - - -
    -
    - {{#showAvailableLanguagesInPageTop}} - - {{/showAvailableLanguagesInPageTop}} -
    -
    - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/payment/mollie.ms b/src/main/webapp/WEB-INF/templates/event/payment/mollie.ms deleted file mode 100644 index bd8683885d..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/payment/mollie.ms +++ /dev/null @@ -1 +0,0 @@ -
    {{#i18n}}reservation-page.mollie.description{{/i18n}}
    \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/payment/offline.ms b/src/main/webapp/WEB-INF/templates/event/payment/offline.ms deleted file mode 100644 index bde068d407..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/payment/offline.ms +++ /dev/null @@ -1,12 +0,0 @@ -
    -
    -
    {{#i18n}}reservation-page.offline.description [{{delayForOfflinePayment}}]{{/i18n}}
    -
    -{{#captchaRequestedForOffline}} -
    -
    -
    -{{/captchaRequestedForOffline}} -
    - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/payment/on-site.ms b/src/main/webapp/WEB-INF/templates/event/payment/on-site.ms deleted file mode 100644 index 55a42eb2c1..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/payment/on-site.ms +++ /dev/null @@ -1,11 +0,0 @@ -
    -
    -
    {{#i18n}}reservation-page.on-site.description{{/i18n}}
    -
    -{{#captchaRequestedForOffline}} -
    -
    -
    -{{/captchaRequestedForOffline}} -
    - diff --git a/src/main/webapp/WEB-INF/templates/event/payment/paypal.ms b/src/main/webapp/WEB-INF/templates/event/payment/paypal.ms deleted file mode 100644 index 4edd36f341..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/payment/paypal.ms +++ /dev/null @@ -1,13 +0,0 @@ -
    - {{#i18n}}reservation-page.paypal.description{{/i18n}} - {{#demoModeEnabled}} -

    {{#i18n}}reservation-page.paypal.demo [{{paypalTestUsername}}] [{{paypalTestPassword}}]{{/i18n}}

    - {{/demoModeEnabled}} -
    - - -{{#tokenAcquired}} -
    -

    {{#i18n}}reservation-page.paypal.confirm{{/i18n}}

    -
    -{{/tokenAcquired}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/payment/stripe.ms b/src/main/webapp/WEB-INF/templates/event/payment/stripe.ms deleted file mode 100644 index 1ac302eb21..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/payment/stripe.ms +++ /dev/null @@ -1,85 +0,0 @@ -
    -
    -
    {{#i18n}}reservation-page.credit-card.description{{/i18n}}
    - {{#demoModeEnabled}} -
    {{#i18n}}reservation-page.credit-card.description.demo{{/i18n}}
    - {{/demoModeEnabled}} -
    - -{{#if-config-flag}}[STRIPE_ENABLE_SCA] -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - - - -{{/if-config-flag}} -{{^if-config-flag}}[STRIPE_ENABLE_SCA] - - -{{/if-config-flag}} diff --git a/src/main/webapp/WEB-INF/templates/event/reservation-page-complete.ms b/src/main/webapp/WEB-INF/templates/event/reservation-page-complete.ms deleted file mode 100644 index 9570295296..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/reservation-page-complete.ms +++ /dev/null @@ -1,116 +0,0 @@ -{{>/event/page-top}} - -{{>/event/header}} - -
    - -

    - {{^additionalServicesOnly}} - {{#ticketsAreAllAssigned}} - {{#i18n}}reservation-page-complete.your-tickets [{{event.displayName}}]{{/i18n}} - {{/ticketsAreAllAssigned}} - {{^ticketsAreAllAssigned}} - {{#i18n}}reservation-page-complete.assign-your-tickets [{{event.displayName}}]{{/i18n}} - {{/ticketsAreAllAssigned}} - {{/additionalServicesOnly}} -

    - -{{#confirmationEmailSent}} - -{{/confirmationEmailSent}} -{{#ticketEmailSent}} - -{{/ticketEmailSent}} - - -
    - {{#additionalServicesOnly}} -
    -
    -

    {{#i18n}}reservation-page-complete.thanks-for-your-support{{/i18n}}

    -
    -
    - {{/additionalServicesOnly}} - {{^additionalServicesOnly}} -
    -
    -

    {{#i18n}}reservation-page-complete.info-assign [{{event.displayName}}]{{/i18n}}

    -
    -
    - {{/additionalServicesOnly}} -
    -
    - {{#i18n}}reservation-page-complete.info-assign-email [{{reservation.email}}]{{/i18n}} -
    -
    -
    -
    -
    - - {{#reservation.hasBeenPaid}} - {{#reservation.hasInvoiceOrReceiptDocument}} - {{#userCanDownloadReceiptOrInvoice}} - {{#reservation.hasInvoiceNumber}} - {{#i18n}}reservation-page-complete.download-your-invoice{{/i18n}} - {{/reservation.hasInvoiceNumber}} - {{^reservation.hasInvoiceNumber}} - {{#i18n}}reservation-page-complete.download-your-receipt{{/i18n}} - {{/reservation.hasInvoiceNumber}} - {{/userCanDownloadReceiptOrInvoice}} - {{/reservation.hasInvoiceOrReceiptDocument}} - {{/reservation.hasBeenPaid}} - -
    - -
    -
    -
    - -{{^additionalServicesOnly}} - {{#displayTransferInfo}} -

    {{#i18n}}reservation-page-complete.info-update{{/i18n}}

    - {{/displayTransferInfo}} -{{/additionalServicesOnly}} - -
      -{{#ticketsByCategory}} - {{#value}} -
    • -
      -

      {{#i18n}}reservation-page-complete.ticket-nr{{/i18n}}

      -
      - {{#i18n}}reservation-page-complete.ticket-type{{/i18n}} {{key.name}} -
      - {{> /event/assign-ticket-form}} -
      -
    • - {{/value}} -{{/ticketsByCategory}} -
    - -
    -
    - {{#i18n}}reservation-page-complete.order-information [{{reservation.id}}] [{{reservation.fullName}}]{{/i18n}} -
    - - - - - - - - -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/reservation-page-error-status.ms b/src/main/webapp/WEB-INF/templates/event/reservation-page-error-status.ms deleted file mode 100644 index 1bfe32fcad..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/reservation-page-error-status.ms +++ /dev/null @@ -1,11 +0,0 @@ -{{>/event/page-top}} - -
    - {{>/event/output-event-image-tag}} -
    - -

    {{#i18n}}reservation-page-error-status.title{{/i18n}}

    -

    {{#i18n}}reservation-page-error-status.contact-text [{{organizer.email}}] [{{reservation.id}}] [{{organizer.email}}]{{/i18n}}

    -

    {{#i18n}}reservation-page-error-status.your-reservation-identifier [{{reservation.id}}]{{/i18n}}

    - -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/reservation-page-not-found.ms b/src/main/webapp/WEB-INF/templates/event/reservation-page-not-found.ms deleted file mode 100644 index 79c31a7bad..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/reservation-page-not-found.ms +++ /dev/null @@ -1,10 +0,0 @@ -{{>/event/page-top}} - -
    - {{>/event/output-event-image-tag}} -
    -
    -

    {{#i18n}}reservation-page-not-found.reservation-with-id-does-not-exist [{{reservationId}}]{{/i18n}}

    -

    {{#i18n}}reservation-page-not-found.go-back-to-event [{{request.contextPath}}/event/{{event.shortName}}/]{{/i18n}}

    - -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/reservation-page.ms b/src/main/webapp/WEB-INF/templates/event/reservation-page.ms deleted file mode 100644 index d2fd7c2b49..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/reservation-page.ms +++ /dev/null @@ -1,156 +0,0 @@ -{{>/event/page-top}} - - - - - - -{{>/event/header}} - -
    -
    -
    {{#i18n}}breadcrumb.step1{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step2{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step3{{#event.free}}.free{{/event.free}}{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step4{{/i18n}}
    -
    -
    - -{{#hasErrors}} - {{#error.globalErrors}} - - {{/error.globalErrors}} -{{/hasErrors}} - -
    - - - -
    - -
    - - - {{#useFirstAndLastName}} -
    -
    -
    - - - {{#field-has-error}}[firstName]{{#i18n}}{{#field-error}}firstName{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    -
    - - - {{#field-has-error}}[lastName]{{#i18n}}{{#field-error}}lastName{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    - {{/useFirstAndLastName}} - {{^useFirstAndLastName}} -
    - - - {{#field-has-error}}[fullName]{{#i18n}}{{#field-error}}fullName{{/field-error}}{{/i18n}}{{/field-has-error}} -
    - {{/useFirstAndLastName}} - - -
    -
    -
    - - - {{#field-has-error}}[email]{{#i18n}}{{#field-error}}email{{/field-error}}{{/i18n}}{{/field-has-error}} -
    -
    -
    - - {{#onlyInvoice}} - {{ > /event/invoice-fields }} - - {{/onlyInvoice}} - {{^orderSummary.free}} - {{#invoiceIsAllowed}} - {{^onlyInvoice}} -
    - -
    - - - {{/onlyInvoice}} - {{/invoiceIsAllowed}} - {{/orderSummary.free}} - - - - {{#showPostpone}} -
    - -
    - {{/showPostpone}} - -
    -
      - {{#ticketsByCategory}} - {{#value}} -
    • -

      {{#i18n}}reservation-page-complete.ticket-nr{{/i18n}} {{^attendeeAutocompleteEnabled}}{{#-first}}{{/-first}}{{/attendeeAutocompleteEnabled}}

      -
      -
      - {{> /event/attendee-fields }} -
      -
      -
    • - {{/value}} - {{/ticketsByCategory}} -
    -
    -

    * {{#i18n}}common.required-fields{{/i18n}}

    -
    - -
    -
    -
    -
    - - - - - -
    - -
    -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/reservation-processing-payment.ms b/src/main/webapp/WEB-INF/templates/event/reservation-processing-payment.ms deleted file mode 100644 index e406e2d1c5..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/reservation-processing-payment.ms +++ /dev/null @@ -1,79 +0,0 @@ -{{>/event/page-top}} - - -{{>/event/header}} - -
    -
    -
    {{#i18n}}breadcrumb.step1{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step2{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step3{{#event.free}}.free{{/event.free}}{{/i18n}}
    -
    -
    -
    -
    {{#i18n}}breadcrumb.step4{{/i18n}}
    -
    -
    - - - - - - - - - - - - - - {{#orderSummary.summary}} - - - - - - - {{/orderSummary.summary}} - - - {{^orderSummary.free}} - {{#orderSummary.displayVat}} - {{^event.vatIncluded}} - - {{/event.vatIncluded}} - {{/orderSummary.displayVat}} - {{/orderSummary.free}} - - - {{^orderSummary.free}} - {{#orderSummary.displayVat}} - {{#event.vatIncluded}} - - {{/event.vatIncluded}} - {{/orderSummary.displayVat}} - {{^orderSummary.displayVat}} - - {{/orderSummary.displayVat}} - {{/orderSummary.free}} - -
    {{#i18n}}reservation-page.category{{/i18n}}{{#i18n}}reservation-page.amount{{/i18n}}{{#i18n}}reservation-page.price{{/i18n}}{{#i18n}}reservation-page.subtotal{{/i18n}}
    {{name}}{{amount}}{{price}}{{subTotal}} {{event.currency}}
    {{#i18n}}reservation-page.vat [{{event.vat}}] [{{vatTranslation}}]{{/i18n}}{{orderSummary.totalVAT}} {{event.currency}}
    {{#i18n}}reservation-page.total{{/i18n}}{{orderSummary.totalPrice}} {{event.currency}}
    {{#i18n}}reservation-page.vat-included [{{event.vat}}] [{{vatTranslation}}]{{/i18n}}{{orderSummary.totalVAT}} {{event.currency}}
    {{#i18n}}invoice.vat-voided [{{vatTranslation}}]{{/i18n}}
    - -
    -

    {{#i18n}}reservation.payment-in-progress{{/i18n}}

    -
    - - -{{>/event/page-bottom}} diff --git a/src/main/webapp/WEB-INF/templates/event/reservation-waiting-for-payment.ms b/src/main/webapp/WEB-INF/templates/event/reservation-waiting-for-payment.ms deleted file mode 100644 index 568fb0a505..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/reservation-waiting-for-payment.ms +++ /dev/null @@ -1,51 +0,0 @@ -{{>/event/page-top}} - -{{>/event/header}} - -
    -

    {{#i18n}}reservation-page-waiting.title [{{#format-date}}{{expires}} {{#i18n}}datetime.pattern{{/i18n}} locale:{{#i18n}}locale{{/i18n}}{{/format-date}}]{{/i18n}}

    -
    - -
     
    - -
    -

    {{#i18n}}reservation-page-waiting.required-steps{{/i18n}}

    -
      - {{#hasBankAccountOwnerSet}} -
    1. -

      {{#i18n}}reservation-page-waiting.required-steps.1.with-bank-account-owner [{{event.currency}} {{totalPrice}}] [{{bankAccount}}]{{/i18n}}

      - {{#bankAccountOwner}}{{/bankAccountOwner}} -

      {{#i18n}}reservation-page-waiting.required-steps.1.with-bank-account-owner.2 [{{paymentReason}}]{{/i18n}}

      -
    2. - {{/hasBankAccountOwnerSet}} - {{^hasBankAccountOwnerSet}} -
    3. {{#i18n}}reservation-page-waiting.required-steps.1 [{{event.currency}} {{totalPrice}}] [{{bankAccount}}] [{{paymentReason}}]{{/i18n}}
    4. - {{/hasBankAccountOwnerSet}} -
    5. {{#i18n}}reservation-page-waiting.required-steps.2 [{{emailAddress}}] [{{reservation.id}}]{{/i18n}}
    6. -
    7. {{#i18n}}reservation-page-waiting.required-steps.3{{/i18n}}
    8. -
    -
    -
    -

    {{#i18n}}reservation-page-waiting.questions [{{emailAddress}}] [{{reservation.id}}]{{/i18n}}

    -
    -
     
    -
    - - {{#userCanDownloadReceiptOrInvoice}} - {{#reservation.hasInvoiceNumber}} - - {{/reservation.hasInvoiceNumber}} - {{/userCanDownloadReceiptOrInvoice}} - {{^userCanDownloadReceiptOrInvoice}} -
    - {{#i18n}}reservation-page-waiting.invoice-will-be-sent{{/i18n}} -
    - {{/userCanDownloadReceiptOrInvoice}} -
    -
    -
    - {{#i18n}}reservation-page-complete.order-information [{{reservation.id}}] [{{reservation.fullName}}]{{/i18n}} -
    -{{>/event/page-bottom}} diff --git a/src/main/webapp/WEB-INF/templates/event/session-expired.ms b/src/main/webapp/WEB-INF/templates/event/session-expired.ms deleted file mode 100644 index f48d49cd32..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/session-expired.ms +++ /dev/null @@ -1,7 +0,0 @@ -{{>/event/page-top}} - -
    -

    {{#i18n}}error.WRONG_CSRF_TOKEN{{/i18n}}

    -

    {{#i18n}}to-home{{/i18n}}

    - -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/show-event.ms b/src/main/webapp/WEB-INF/templates/event/show-event.ms deleted file mode 100644 index d10c32666f..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/show-event.ms +++ /dev/null @@ -1,223 +0,0 @@ -{{>/event/page-top}} - -{{>/event/header}} - -
    -
    - {{>/event/event-details}} -
    - {{#locationDescriptor.hasMapUrl}} -
    - -
    - {{/locationDescriptor.hasMapUrl}} -
    - -
    - -
    -
    -
    {{#commonmark}}{{event.description}}{{/commonmark}}
    -
    -
    - -

    {{#i18n}}show-event.tickets{{/i18n}}

    - -{{#hasErrors}} - -{{/hasErrors}} - -{{#displayWaitingQueueForm}} - {{^subscriptionComplete}} - - {{/subscriptionComplete}} - {{#subscriptionComplete}} - {{>/event/waiting-queue-subscription-result.ms}} - {{/subscriptionComplete}} -{{/displayWaitingQueueForm}} - -
    -
      - {{#ticketCategories}} - {{> /event/ticket-category.ms }} - {{/ticketCategories}} -
    - {{#showAdditionalServices}} - {{#showAdditionalServicesSupplements}} -

    {{#i18n}}show-event.additional-services{{/i18n}}

    -
      - {{#enabledAdditionalServicesSupplements}} - {{> /event/additional-service.ms }} - {{/enabledAdditionalServicesSupplements}} -
    - {{/showAdditionalServicesSupplements}} - {{#showAdditionalServicesDonations}} -

    {{#i18n}}show-event.donations{{/i18n}}

    -
      - {{#enabledAdditionalServicesDonations}} - {{> /event/additional-service.ms }} - {{/enabledAdditionalServicesDonations}} -
    - {{/showAdditionalServicesDonations}} - {{/showAdditionalServices}} - {{#showNoCategoriesWarning}} -
    -

    {{#i18n}}show-event.sold-out.header{{/i18n}}

    -
    - {{/showNoCategoriesWarning}} - {{#containsExpiredCategories}} -
    - -
    -
      - {{#expiredCategories}} - {{> /event/ticket-category.ms }} - {{/expiredCategories}} -
    -
    -
    - {{/containsExpiredCategories}} - -{{#hasPromoCodeDiscount}} - -
    - -
    - -
      -
    • -
      -
      {{#i18n}}show-event.promo-code-applied [{{promoCodeDescription}}]{{/i18n}} {{promoCodeDiscount.promoCode}}
      -
      - {{#promoCodeDiscount.fixedAmount}} - {{#i18n}}show-event.promo-code-fixed-amount-discount [{{promoCodeDiscount.formattedDiscountAmount}} {{event.currency}}]{{/i18n}} - {{/promoCodeDiscount.fixedAmount}} - {{^promoCodeDiscount.fixedAmount}} - {{#i18n}}show-event.promo-code-percentage-discount [{{promoCodeDiscount.discountAmount}}%]{{/i18n}} - {{/promoCodeDiscount.fixedAmount}} -
      -
      -
    • -
    -
    -{{/hasPromoCodeDiscount}} - -
    -{{#hasAccessPromotions}} -
    -
    - -
    -
    - -
    - - -
    -
    -
    -
    -
    -{{/hasAccessPromotions}} -{{#captchaForTicketSelectionEnabled}} -
    -{{/captchaForTicketSelectionEnabled}} -
    - - - - -
    -
    - -
    - -
    -
    - - - -{{#displayWaitingQueueForm}} - -{{/displayWaitingQueueForm}} - - - -{{#captchaForTicketSelectionEnabled}} - -{{/captchaForTicketSelectionEnabled}} - -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/show-ticket.ms b/src/main/webapp/WEB-INF/templates/event/show-ticket.ms deleted file mode 100644 index 8a43d9560d..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/show-ticket.ms +++ /dev/null @@ -1,58 +0,0 @@ -{{>/event/page-top}} -{{>/event/header}} - -{{#ticketEmailSent}} - -{{/ticketEmailSent}} -
    -
    -
    - {{>/event/event-details}} -
    -
    - -
    -
    - -
    -

    {{#i18n}}ticket.ticket{{/i18n}}

    -
    -
    {{#i18n}}ticket.holder{{/i18n}}
    -
    {{ticket.fullName}} <{{ticket.email}}>
    -
    {{#i18n}}ticket.type{{/i18n}}
    -
    {{ticketCategory.name}}
    -
    {{#i18n}}ticket.reference-number{{/i18n}}
    -
    {{ticket.uuid}}
    -
    {{#i18n}}ticket.order-information{{/i18n}}
    -
    {{#i18n}}ticket.order-information-values [{{reservationId}}] [{{reservation.fullName}}]{{/i18n}}
    -
    -
    - - - {{#deskPaymentRequired}} -
    -

    {{#i18n}}ticket.payment-required{{/i18n}}

    -
    - {{/deskPaymentRequired}} - - -
    - - - - - -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/ticket-category.ms b/src/main/webapp/WEB-INF/templates/event/ticket-category.ms deleted file mode 100644 index 59dbc96312..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/ticket-category.ms +++ /dev/null @@ -1,66 +0,0 @@ -
  • -
    -
    - - {{^expired}} -
    - {{#saleInFuture}} - {{#i18n}}show-event.sales-not-started [{{#format-date}}{{zonedInception}} dd.MM.yyyy HH:mm locale:{{#i18n}}locale{{/i18n}}{{/format-date}}]{{/i18n}} - {{/saleInFuture}} - {{^saleInFuture}} - {{#i18n}}show-event.sales-end [{{#format-date}}{{zonedExpiration}} dd.MM.yyyy HH:mm locale:{{#i18n}}locale{{/i18n}}{{/format-date}}]{{/i18n}} - {{/saleInFuture}} -
    - {{/expired}} - {{#expired}} -
    - {{#i18n}}show-event.sales-ended [{{#format-date}}{{zonedExpiration}} dd.MM.yyyy HH:mm locale:{{#i18n}}locale{{/i18n}}{{/format-date}}]{{/i18n}} -
    - {{/expired}} -
    - {{#commonmark}}{{description}}{{/commonmark}} -
    -
    -
    - -
    -
    - {{#saleableAndLimitNotReached}} - - - {{/saleableAndLimitNotReached}} - {{^saleableAndLimitNotReached}} - - {{/saleableAndLimitNotReached}} -
    -
    -
  • \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/update-ticket.ms b/src/main/webapp/WEB-INF/templates/event/update-ticket.ms deleted file mode 100644 index 5cffda4c3b..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/update-ticket.ms +++ /dev/null @@ -1,47 +0,0 @@ -{{>/event/page-top}} - -{{>/event/header}} - -
    - -

    - {{#i18n}}show-ticket.header.title [{{event.displayName}}]{{/i18n}} -

    - -{{#ticketEmailSent}} - -{{/ticketEmailSent}} -{{#transferEnabled}} -

    {{#i18n}}reservation-page-complete.info-update{{/i18n}}

    -{{/transferEnabled}} - -
      - {{#ticketAndCategory}} - {{#value}} -
    • -
      -

      {{#i18n}}reservation-page-complete.ticket-nr{{/i18n}}

      -
      - {{#i18n}}reservation-page-complete.ticket-type{{/i18n}} {{key.name}} -
      - {{> /event/assign-ticket-form}} -
      -
    • - {{/value}} - {{/ticketAndCategory}} -
    -
    -
    - {{#i18n}}reservation-page-complete.order-information [{{reservationId}}] [{{reservation.fullName}}]{{/i18n}} -
    - - - - - - - -{{>/event/page-bottom}} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/waiting-queue-subscription-result.ms b/src/main/webapp/WEB-INF/templates/event/waiting-queue-subscription-result.ms deleted file mode 100644 index 4177c6732c..0000000000 --- a/src/main/webapp/WEB-INF/templates/event/waiting-queue-subscription-result.ms +++ /dev/null @@ -1,10 +0,0 @@ -{{#error}} - -{{/error}} -{{^error}} - -{{/error}} diff --git a/src/main/webapp/resources/css/application.css b/src/main/webapp/resources/css/application.css deleted file mode 100644 index 3e935cccfe..0000000000 --- a/src/main/webapp/resources/css/application.css +++ /dev/null @@ -1,296 +0,0 @@ -body { - background-color: #E5E5E5; -} - - -div.col-md-8.col-md-offset-2 { - background-color:white; -} - -div.col-md-8.col-md-offset-2 > div { - padding-top:1em; - padding-bottom:1em; -} - -.text-align-center { - text-align: center; -} - -.text-align-right { - text-align: right; -} - -.event-description { - margin-top:1em; -} - -.img-center { - margin:auto; -} - -div.header img.img-responsive { - max-height: 100px; -} - -.alfio-lang { - background-color:transparent; -} - -.event-expired { - color:#bbb; -} - -p.ticket-info { - padding:15px; - margin-top:10px; -} - -.hide-by-default { - display: none; -} - -.show-by-default { - -} - -.no-margin-bottom { - margin-bottom:0; -} - -.bank-account-owner-info { - padding-left:1em; - margin-bottom:0; -} - - -.list-group-item { - border-left:none; - border-right:none; -} - -.list-group-item:first-child { - border-top:none; -} - -.list-group-item:last-child { - border-bottom:none; -} - -.ticket-category-restricted-true { - background-color: #F5F5DC -} - -.center { - text-align: center; -} - -.wMarginBottom { - margin-bottom: 20px; -} - -.wMarginTop { - margin-top:20px; -} - -body { - counter-reset: ticket-counter; -} - -.ticket-counter:before { - counter-increment: ticket-counter; - content: counter(ticket-counter) -} - -.ticket-header-col { - width:150px; - vertical-align: top; -} - -.payment-method-container { - width:80%; -} - -.alfio-event-add-to-calendar { - padding-left: 13px; -} - -.alfio-event-add-to-calendar > span:before { - content: "\00a0 " -} - -a.no-decoration { - text-decoration:none; -} - -@media screen and (max-width: 767px) { - .xs-payment-method { - width:100%; - margin-bottom: 5px; - } - .j-btn-group { - margin-bottom: 10px; - width: 100%; - } - .wizard a:not(current) {padding: 12px 12px 10px 12px; background:#efefef; position:relative; display:inline-block; width:20%} - .wizard a.current {width:50%} -} - -@media screen and (min-width: 768px) { - li img.logo { - margin-top:20px; - } - .j-btn-group { - display: table; - width: 100%; - table-layout: fixed; - border-collapse: separate; - } - .j-btn-group > .btn, - .j-btn-group > .btn-group { - display: table-cell; - float: none; - width: 1%; - } - .border-left { - margin-left: 15px; - padding-left: 40px; - border-left: 1px solid #ddd; - } -} - -div.preformatted { - word-wrap: break-word; - white-space: pre-wrap; -} - -.badge-inverse {color:#777; background-color: white} - -.wMarginTop30px { - margin-top: 30px; -} -.wMarginBottom30px { - margin-bottom: 30px; -} - -.alfio-lang a { -} - -html[lang=nl] .alfio-lang a[lang=nl], -html[lang=de] .alfio-lang a[lang=de], -html[lang=en] .alfio-lang a[lang=en], -html[lang=it] .alfio-lang a[lang=it], -html[lang=fr] .alfio-lang a[lang=fr], -html[lang=ro] .alfio-lang a[lang=ro], -html[lang=pt] .alfio-lang a[lang=pt] { - font-weight:bold; -} - -.form-horizontal .control-label.category-name { - text-align:left; -} - -.checkbox-in-form-group { - margin-top:30px; -} - -.checkbox-in-form-group label { - padding-left:20px; - font-weight:normal; -} - -.label-after-radio { - padding-left: 6px; - position: relative; - bottom: 2px; -} - -.show-event.g-recaptcha, .overview.g-recaptcha { - text-align:center; -} -.show-event.g-recaptcha > div, .overview.g-recaptcha > div { - display:inline-block; -} - -/* based on https://material-ui.com/demos/steppers/ */ -.wizard2 { - display: flex; - flex-direction: row; - align-items: flex-start; - margin: 4rem 0; -} - -.wizard2 > div { - flex: 1; - position:relative -} - -.wizard2 > div > div:first-child { - flex-direction: column; - display: flex; - align-items: center; -} - -.wizard2 .badge { - display: inline-block; - min-width: 35px; - padding: 3px 7px; - font-size: 14px; - font-weight: 700; - line-height: 2; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: top; - background-color: #777; - border-radius: 17px; - min-height: 30px; -} - -.wizard2 .wizard2-line { - top: 17px; - left: calc(50% + 20px); - right: calc(-50% + 20px); - position: absolute; - border-top: 1px solid #bdbdbd; -} - -.badge-success { - background-color: #449d44 !important; -} - -.wizard2 .wizard2-current .badge { - background-color: #007ACC; -} - -.wizard2 span:nth-child(2) { - margin-top: 1em; -} - -.wizard2 .wizard2-current span:nth-child(2) { - font-weight:bold; - color: black; -} - -/* source: https://stackoverflow.com/a/24349758 */ - -@keyframes dots-1 { from { opacity: 0; } 25% { opacity: 1; } } -@keyframes dots-2 { from { opacity: 0; } 50% { opacity: 1; } } -@keyframes dots-3 { from { opacity: 0; } 75% { opacity: 1; } } -@-webkit-keyframes dots-1 { from { opacity: 0; } 25% { opacity: 1; } } -@-webkit-keyframes dots-2 { from { opacity: 0; } 50% { opacity: 1; } } -@-webkit-keyframes dots-3 { from { opacity: 0; } 75% { opacity: 1; } } - -.dots span { - animation: dots-1 1s infinite steps(1); - -webkit-animation: dots-1 1s infinite steps(1); -} - -.dots span:first-child + span { - animation-name: dots-2; - -webkit-animation-name: dots-2; -} - -.dots span:first-child + span + span { - animation-name: dots-3; - -webkit-animation-name: dots-3; -} \ No newline at end of file diff --git a/src/main/webapp/resources/css/payment/stripe.css b/src/main/webapp/resources/css/payment/stripe.css deleted file mode 100644 index b5c8a7d33a..0000000000 --- a/src/main/webapp/resources/css/payment/stripe.css +++ /dev/null @@ -1,30 +0,0 @@ -.StripeElement { - box-sizing: border-box; - - height: 34px; - - padding: 8px 12px; - - border: 1px solid #ccc; - border-radius: 4px; - background-color: white; - - /*box-shadow: 0 1px 3px 0 #e6ebf1;*/ - -webkit-transition: box-shadow 150ms ease; - transition: box-shadow 150ms ease; -} - -.StripeElement--focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6); - box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6); -} - -.StripeElement--invalid { - border-color: #a94442; -} - -.StripeElement--webkit-autofill { - background-color: #fefde5 !important; -} diff --git a/src/main/webapp/resources/js/countdownjs/countdown.min.js b/src/main/webapp/resources/js/countdownjs/countdown.min.js deleted file mode 100644 index 606ce7630e..0000000000 --- a/src/main/webapp/resources/js/countdownjs/countdown.min.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - countdown.js v2.6.0 http://countdownjs.org - Copyright (c)2006-2014 Stephen M. McKamey. - Licensed under The MIT License. -*/ -var module,countdown=function(v){function A(a,b){var c=a.getTime();a.setMonth(a.getMonth()+b);return Math.round((a.getTime()-c)/864E5)}function w(a){var b=a.getTime(),c=new Date(b);c.setMonth(a.getMonth()+1);return Math.round((c.getTime()-b)/864E5)}function x(a,b){b=b instanceof Date||null!==b&&isFinite(b)?new Date(+b):new Date;if(!a)return b;var c=+a.value||0;if(c)return b.setTime(b.getTime()+c),b;(c=+a.milliseconds||0)&&b.setMilliseconds(b.getMilliseconds()+c);(c=+a.seconds||0)&&b.setSeconds(b.getSeconds()+ -c);(c=+a.minutes||0)&&b.setMinutes(b.getMinutes()+c);(c=+a.hours||0)&&b.setHours(b.getHours()+c);(c=+a.weeks||0)&&(c*=7);(c+=+a.days||0)&&b.setDate(b.getDate()+c);(c=+a.months||0)&&b.setMonth(b.getMonth()+c);(c=+a.millennia||0)&&(c*=10);(c+=+a.centuries||0)&&(c*=10);(c+=+a.decades||0)&&(c*=10);(c+=+a.years||0)&&b.setFullYear(b.getFullYear()+c);return b}function D(a,b){return y(a)+(1===a?p[b]:q[b])}function n(){}function k(a,b,c,e,l,d){0<=a[c]&&(b+=a[c],delete a[c]);b/=l;if(1>=b+1)return 0;if(0<=a[e]){a[e]= -+(a[e]+b).toFixed(d);switch(e){case "seconds":if(60!==a.seconds||isNaN(a.minutes))break;a.minutes++;a.seconds=0;case "minutes":if(60!==a.minutes||isNaN(a.hours))break;a.hours++;a.minutes=0;case "hours":if(24!==a.hours||isNaN(a.days))break;a.days++;a.hours=0;case "days":if(7!==a.days||isNaN(a.weeks))break;a.weeks++;a.days=0;case "weeks":if(a.weeks!==w(a.refMonth)/7||isNaN(a.months))break;a.months++;a.weeks=0;case "months":if(12!==a.months||isNaN(a.years))break;a.years++;a.months=0;case "years":if(10!== -a.years||isNaN(a.decades))break;a.decades++;a.years=0;case "decades":if(10!==a.decades||isNaN(a.centuries))break;a.centuries++;a.decades=0;case "centuries":if(10!==a.centuries||isNaN(a.millennia))break;a.millennia++;a.centuries=0}return 0}return b}function B(a,b,c,e,l,d){var f=new Date;a.start=b=b||f;a.end=c=c||f;a.units=e;a.value=c.getTime()-b.getTime();0>a.value&&(f=c,c=b,b=f);a.refMonth=new Date(b.getFullYear(),b.getMonth(),15,12,0,0);try{a.millennia=0;a.centuries=0;a.decades=0;a.years=c.getFullYear()- -b.getFullYear();a.months=c.getMonth()-b.getMonth();a.weeks=0;a.days=c.getDate()-b.getDate();a.hours=c.getHours()-b.getHours();a.minutes=c.getMinutes()-b.getMinutes();a.seconds=c.getSeconds()-b.getSeconds();a.milliseconds=c.getMilliseconds()-b.getMilliseconds();var g;0>a.milliseconds?(g=s(-a.milliseconds/1E3),a.seconds-=g,a.milliseconds+=1E3*g):1E3<=a.milliseconds&&(a.seconds+=m(a.milliseconds/1E3),a.milliseconds%=1E3);0>a.seconds?(g=s(-a.seconds/60),a.minutes-=g,a.seconds+=60*g):60<=a.seconds&&(a.minutes+= -m(a.seconds/60),a.seconds%=60);0>a.minutes?(g=s(-a.minutes/60),a.hours-=g,a.minutes+=60*g):60<=a.minutes&&(a.hours+=m(a.minutes/60),a.minutes%=60);0>a.hours?(g=s(-a.hours/24),a.days-=g,a.hours+=24*g):24<=a.hours&&(a.days+=m(a.hours/24),a.hours%=24);for(;0>a.days;)a.months--,a.days+=A(a.refMonth,1);7<=a.days&&(a.weeks+=m(a.days/7),a.days%=7);0>a.months?(g=s(-a.months/12),a.years-=g,a.months+=12*g):12<=a.months&&(a.years+=m(a.months/12),a.months%=12);10<=a.years&&(a.decades+=m(a.years/10),a.years%= -10,10<=a.decades&&(a.centuries+=m(a.decades/10),a.decades%=10,10<=a.centuries&&(a.millennia+=m(a.centuries/10),a.centuries%=10)));b=0;!(e&1024)||b>=l?(a.centuries+=10*a.millennia,delete a.millennia):a.millennia&&b++;!(e&512)||b>=l?(a.decades+=10*a.centuries,delete a.centuries):a.centuries&&b++;!(e&256)||b>=l?(a.years+=10*a.decades,delete a.decades):a.decades&&b++;!(e&128)||b>=l?(a.months+=12*a.years,delete a.years):a.years&&b++;!(e&64)||b>=l?(a.months&&(a.days+=A(a.refMonth,a.months)),delete a.months, -7<=a.days&&(a.weeks+=m(a.days/7),a.days%=7)):a.months&&b++;!(e&32)||b>=l?(a.days+=7*a.weeks,delete a.weeks):a.weeks&&b++;!(e&16)||b>=l?(a.hours+=24*a.days,delete a.days):a.days&&b++;!(e&8)||b>=l?(a.minutes+=60*a.hours,delete a.hours):a.hours&&b++;!(e&4)||b>=l?(a.seconds+=60*a.minutes,delete a.minutes):a.minutes&&b++;!(e&2)||b>=l?(a.milliseconds+=1E3*a.seconds,delete a.seconds):a.seconds&&b++;if(!(e&1)||b>=l){var h=k(a,0,"milliseconds","seconds",1E3,d);if(h&&(h=k(a,h,"seconds","minutes",60,d))&&(h= -k(a,h,"minutes","hours",60,d))&&(h=k(a,h,"hours","days",24,d))&&(h=k(a,h,"days","weeks",7,d))&&(h=k(a,h,"weeks","months",w(a.refMonth)/7,d))){e=h;var n,p=a.refMonth,q=p.getTime(),r=new Date(q);r.setFullYear(p.getFullYear()+1);n=Math.round((r.getTime()-q)/864E5);if(h=k(a,e,"months","years",n/w(a.refMonth),d))if(h=k(a,h,"years","decades",10,d))if(h=k(a,h,"decades","centuries",10,d))if(h=k(a,h,"centuries","millennia",10,d))throw Error("Fractional unit overflow");}}}finally{delete a.refMonth}return a} -function d(a,b,c,e,d){var f;c=+c||222;e=0d?Math.round(d):20:0;var k=null;"function"===typeof a?(f=a,a=null):a instanceof Date||(null!==a&&isFinite(a)?a=new Date(+a):("object"===typeof k&&(k=a),a=null));var g=null;"function"===typeof b?(f=b,b=null):b instanceof Date||(null!==b&&isFinite(b)?b=new Date(+b):("object"===typeof b&&(g=b),b=null));k&&(a=x(k,b));g&&(b=x(g,a));if(!a&&!b)return new n;if(!f)return B(new n,a,b,c,e,d);var k=c&1?1E3/30:c&2?1E3:c&4?6E4:c&8?36E5:c&16?864E5:6048E5, -h,g=function(){f(B(new n,a,b,c,e,d),h)};g();return h=setInterval(g,k)}var s=Math.ceil,m=Math.floor,p,q,r,t,u,f,y,z;n.prototype.toString=function(a){var b=z(this),c=b.length;if(!c)return a?""+a:u;if(1===c)return b[0];a=r+b.pop();return b.join(t)+a};n.prototype.toHTML=function(a,b){a=a||"span";var c=z(this),e=c.length;if(!e)return(b=b||u)?"\x3c"+a+"\x3e"+b+"\x3c/"+a+"\x3e":b;for(var d=0;d=d;d++)p[d]=b[d]||p[d],q[d]=c[d]||q[d]}"string"===typeof a.last&&(r=a.last);"string"===typeof a.delim&&(t=a.delim);"string"===typeof a.empty&&(u=a.empty);"function"===typeof a.formatNumber&&(y=a.formatNumber);"function"===typeof a.formatter&&(f=a.formatter)}},C=d.resetFormat= -function(){p=" millisecond; second; minute; hour; day; week; month; year; decade; century; millennium".split(";");q=" milliseconds; seconds; minutes; hours; days; weeks; months; years; decades; centuries; millennia".split(";");r=" and ";t=", ";u="";y=function(a){return a};f=D};d.setLabels=function(a,b,c,d,f,k,m){E({singular:a,plural:b,last:c,delim:d,empty:f,formatNumber:k,formatter:m})};d.resetLabels=C;C();v&&v.exports?v.exports=d:"function"===typeof window.define&&"undefined"!==typeof window.define.amd&& -window.define("countdown",[],function(){return d});return d}(module); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/attendee-form.js b/src/main/webapp/resources/js/event/attendee-form.js deleted file mode 100644 index 5bbba0aa92..0000000000 --- a/src/main/webapp/resources/js/event/attendee-form.js +++ /dev/null @@ -1,34 +0,0 @@ -(function() { - - 'use strict'; - - $(function() { - var collapsibleContainer = $('.collapsible-container[data-collapse-enabled="true"]'); - if(collapsibleContainer.length > 1) { - var collapsibleElement = collapsibleContainer.find('.collapsible'); - collapsibleElement.addClass('hidden'); - collapsibleElement.attr('aria-expanded', 'false'); - collapsibleContainer.find('div.toggle-collapse').removeClass('hidden'); - collapsibleContainer.find('a.toggle-collapse').click(function() { - var expanded = collapsibleElement.attr('aria-expanded'); - if(expanded === 'true') { - collapsibleElement.addClass('hidden'); - collapsibleElement.attr('aria-expanded', 'false'); - collapsibleContainer.find('span.collapse-less').addClass('hidden'); - collapsibleContainer.find('span.collapse-more').removeClass('hidden'); - } else { - collapsibleElement.removeClass('hidden'); - collapsibleElement.attr('aria-expanded', 'true'); - collapsibleContainer.find('span.collapse-more').addClass('hidden'); - collapsibleContainer.find('span.collapse-less').removeClass('hidden'); - } - }); - } - $("select").map(function() { - var value = $(this).attr('value'); - if(value && value.length > 0) { - $(this).val(value); - } - }); - }); -})(); diff --git a/src/main/webapp/resources/js/event/bootstrap-handler.js b/src/main/webapp/resources/js/event/bootstrap-handler.js deleted file mode 100644 index f116ed54bb..0000000000 --- a/src/main/webapp/resources/js/event/bootstrap-handler.js +++ /dev/null @@ -1,6 +0,0 @@ -(function() { - 'use strict'; - jQuery(function($) { - $('i.tooltip-handler,button.tooltip-handler').tooltip(); - }); -})(); diff --git a/src/main/webapp/resources/js/event/jquery.shorten.js b/src/main/webapp/resources/js/event/jquery.shorten.js deleted file mode 100644 index 281d39a32e..0000000000 --- a/src/main/webapp/resources/js/event/jquery.shorten.js +++ /dev/null @@ -1,146 +0,0 @@ - -/* - * jQuery Shorten plugin 1.1.0 - * - * Copyright (c) 2014 Viral Patel - * http://viralpatel.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/mit-license.php - */ - -/* - ** updated by Jeff Richardson - ** Updated to use strict, - ** IE 7 has a "bug" It is returning underfined when trying to reference string characters in this format - ** content[i]. IE 7 allows content.charAt(i) This works fine in all modern browsers. - ** I've also added brackets where they werent added just for readability (mostly for me). - */ - -(function($) { - $.fn.shorten = function(settings) { - "use strict"; - - var config = { - showChars: 100, - minHideChars: 10, - ellipsesText: "...", - moreText: "more", - lessText: "less", - errMsg: null, - force: false - }; - - if (settings) { - $.extend(config, settings); - } - - if ($(this).data('jquery.shorten') && !config.force) { - return false; - } - $(this).data('jquery.shorten', true); - - $(document).off("click", '.morelink'); - - $(document).on({ - click: function() { - - var $this = $(this); - if ($this.hasClass('less')) { - $this.removeClass('less'); - $this.html(config.moreText); - $this.parent().prev().animate({'height':'0'+'%'}, function () { $this.parent().prev().prev().show(); }).hide('fast'); - - } else { - $this.addClass('less'); - $this.html(config.lessText); - $this.parent().prev().animate({'height':'100'+'%'}, function () { $this.parent().prev().prev().hide(); }).show('fast'); - } - return false; - } - }, '.morelink'); - - return this.each(function() { - var $this = $(this); - - var content = $this.html(); - var contentlen = $this.text().length; - if (contentlen > config.showChars + config.minHideChars) { - var c = content.substr(0, config.showChars); - if (c.indexOf('<') >= 0) // If there's HTML don't want to cut it - { - var inTag = false; // I'm in a tag? - var bag = ''; // Put the characters to be shown here - var countChars = 0; // Current bag size - var openTags = []; // Stack for opened tags, so I can close them later - var tagName = null; - - for (var i = 0, r = 0; r <= config.showChars; i++) { - if (content[i] == '<' && !inTag) { - inTag = true; - - // This could be "tag" or "/tag" - tagName = content.substring(i + 1, content.indexOf('>', i)); - - // If its a closing tag - if (tagName[0] == '/') { - - - if (tagName != '/' + openTags[0]) { - config.errMsg = 'ERROR en HTML: the top of the stack should be the tag that closes'; - } else { - openTags.shift(); // Pops the last tag from the open tag stack (the tag is closed in the retult HTML!) - } - - } else { - // There are some nasty tags that don't have a close tag like
    - if (tagName.toLowerCase() != 'br') { - openTags.unshift(tagName); // Add to start the name of the tag that opens - } - } - } - if (inTag && content[i] == '>') { - inTag = false; - } - - if (inTag) { bag += content.charAt(i); } // Add tag name chars to the result - else { - r++; - if (countChars <= config.showChars) { - bag += content.charAt(i); // Fix to ie 7 not allowing you to reference string characters using the [] - countChars++; - } else // Now I have the characters needed - { - if (openTags.length > 0) // I have unclosed tags - { - //console.log('They were open tags'); - //console.log(openTags); - for (var j = 0; j < openTags.length; j++) { - //console.log('Cierro tag ' + openTags[j]); - bag += ''; // Close all tags that were opened - - // You could shift the tag from the stack to check if you end with an empty stack, that means you have closed all open tags - } - break; - } - } - } - } - c = $('
    ').html(bag + '' + config.ellipsesText + '').html(); - }else{ - c+=config.ellipsesText; - } - - var html = '
    ' + c + - '
    ' + content + - '
    ' + config.moreText + ''; - - $this.html(html); - $this.find(".allcontent").hide(); // Hide all text - $('.shortcontent p:last', $this).css('margin-bottom', 0); //Remove bottom margin on last paragraph as it's likely shortened - } - }); - - }; - -})(jQuery); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/overview-page.js b/src/main/webapp/resources/js/event/overview-page.js deleted file mode 100644 index 35a4e054ff..0000000000 --- a/src/main/webapp/resources/js/event/overview-page.js +++ /dev/null @@ -1,228 +0,0 @@ -(function() { - - 'use strict'; - var paymentHandlers = []; - - window.alfio = { - registerPaymentHandler: function(handler) { - if(handler.active()) { - handler.init(); - paymentHandlers.push(handler); - } - } - }; - - jQuery(function() { - //validity - //ready for ECMAScript6? - var parser = Number && Number.parseInt ? Number : window; - var validity = new Date(parser.parseInt($("#validity").attr('data-validity'))); - - var displayMessage = function() { - - var validityElem = $("#validity"); - var template = validityElem.attr('data-message'); - - countdown.setLabels( - validityElem.attr('data-labels-singular'), - validityElem.attr('data-labels-plural'), - ' '+validityElem.attr('data-labels-and')+' ', - ', '); - - var timerId = countdown(validity, function(ts) { - if(ts.value < 0) { - validityElem.html(template.replace('##time##', ts.toHTML("strong"))); - } else { - clearInterval(timerId); - $('#validity-container').html(''+validityElem.attr('data-message-time-elapsed')+''); - $('#continue-button').addClass('hidden'); - } - }, countdown.MONTHS|countdown.WEEKS|countdown.DAYS|countdown.HOURS|countdown.MINUTES|countdown.SECONDS); - - }; - - displayMessage(); - - $("#cancel-reservation").click(function(e) { - var $form = $('#payment-form'); - $("input[type=submit]", $form ).unbind('click'); - $form.unbind('submit'); - $("input", $form).unbind('keypress'); - - $form - .attr('novalidate', 'novalidate') - .find('button').prop('disabled', true); - $form.trigger("reset"); - $form.append($('').val(true)) - $form.submit(); - }); - - - function markFieldAsError(node) { - $(node).parent().addClass('has-error'); - var parent = $(node).parent().parent(); - if(parent.hasClass('checkbox') || parent.hasClass('radio')) { - parent.addClass('has-error'); - } else if(parent.parent().hasClass('form-group')) { - parent.parent().addClass('has-error'); - } - } - // based on http://tjvantoll.com/2012/08/05/html5-form-validation-showing-all-error-messages/ - // http://stackoverflow.com/questions/13798313/set-custom-html5-required-field-validation-message - var createAllErrors = function() { - var form = $(this); - - var showAllErrorMessages = function() { - $(form).find('.has-error').removeClass('has-error'); - // Find all invalid fields within the form. - var invalidFields = form.find("input,select,textarea").filter(function(i,v) {return !v.validity.valid;}).each( function( index, node ) { - markFieldAsError(node); - }); - - if(invalidFields.length > 0) { - form.get(0).reportValidity(); - } - }; - - // Support Safari - form.on("submit", function( event ) { - if (this.checkValidity && !this.checkValidity() ) { - $(this).find("input,select,textarea").filter(function(i,v) {return !v.validity.valid;}).first().focus(); - event.preventDefault(); - } - }); - - $("#continue-button", form).on("click", showAllErrorMessages); - - $("input", form).on("keypress", function(event) { - var type = $(this).attr("type"); - if ( /date|email|month|number|search|tel|text|time|url|week/.test(type) && event.keyCode == 13 ) { - showAllErrorMessages(); - } - }); - }; - - $("form").each(createAllErrors); - $("input,select,textarea").change(function() { - if( !this.validity.valid) { - $(this).parent().addClass('has-error'); - if($(this).parent().parent().parent().hasClass('form-group')) { - $(this).parent().parent().parent().addClass('has-error'); - } - } else { - $(this).parent().removeClass('has-error'); - if($(this).parent().parent().parent().hasClass('form-group')) { - $(this).parent().parent().parent().removeClass('has-error'); - } - } - }); - - - var methodSelected = function(method) { - if((method === 'FREE' || method === 'OFFLINE' || method === 'ON_SITE') && window.recaptchaReady) { - $('.g-recaptcha').each(function(i, e) { - try { - grecaptcha.reset(e.id); - } catch(x) {} - }); - try { - grecaptcha.render('captcha-'+method, { - 'sitekey': $('#captcha-'+method).attr('data-sitekey'), - 'hl': $('html').attr('lang') - }); - } catch(x) {} - } - }; - - var setConfirmButtonText = function(method) { - var customTextContainer = method.find('.custom-button-text'); - var button = $('#continue-button'); - var defaultText = button.attr('data-default-text'); - if(customTextContainer.length === 1) { - button.text(customTextContainer.attr('data-button-text')); - } else if(defaultText) { - button.text(defaultText); - } - }; - - var paymentMethod = $('input[name=paymentMethod]'); - if(paymentMethod.length > 1) { - $('#payment-method-STRIPE').find('input').removeAttr('required'); - $('.payment-method-detail').hide(); - - paymentMethod.change(function() { - var method = $(this).attr('data-payment-method'); - $('.payment-method-detail').hide(); - $('#payment-method-'+method).show(); - setConfirmButtonText($(this).parent()); - }); - } else if(paymentMethod.length === 1) { - setConfirmButtonText($(paymentMethod[0]).parent()); - } - - window.recaptchaLoadCallback = function() { - window.recaptchaReady = true; - var methods = $('input[name=paymentMethod]'); - if(methods.length === 1) { - methodSelected(methods.val()); - } else if(methods.length === 0) { - $('#captcha-FREE').each(function(e) { - methodSelected('FREE'); - }); - } - }; - - var btn = $('#continue-button'); - var registerUnloadHook = function() { - console.log("warn on page reload: on"); - // source: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload - window.onbeforeunload = function (e) { - // Cancel the event - e.preventDefault(); - // Chrome requires returnValue to be set - e.returnValue = ''; - }; - }; - var unregisterHook = function() { - console.log("warn on page reload: off"); - window.onbeforeunload = null; - }; - btn.on('click', function(e) { - var $form = $('#payment-form'); - if($form.length === 0 || !$form.get(0).checkValidity()) { - return false; - } - var selectedPaymentMethod = $form.find('input[name=paymentMethod]'); - if(selectedPaymentMethod.length > 1) { - selectedPaymentMethod = selectedPaymentMethod.filter(":checked"); - } - var filteredHandlers = paymentHandlers.filter(function(ph) {return ph.id === selectedPaymentMethod.val() && ph.active(); }); - var paymentHandler = filteredHandlers ? filteredHandlers[0] : null; - if(paymentHandler && paymentHandler.valid()) { - registerUnloadHook(); - var chargeFailedAlert = $('#charge-failed-alert'); - $('#confirm-buttons').addClass('hidden'); - $('#wait-message').removeClass('hidden'); - chargeFailedAlert.addClass('hide'); - paymentHandler.pay(function(res) { - unregisterHook(); - if(res) { - $form.submit(); - } - }, function(errorMessage) { - unregisterHook(); - $('#confirm-buttons').removeClass('hidden'); - $('#wait-message').addClass('hidden'); - if(errorMessage && errorMessage.trim() !== '') { - chargeFailedAlert.removeClass('hide'); - chargeFailedAlert.find('#charge-failed-msg').text(errorMessage); - } - }); - } - e.preventDefault(); - }) - - }); - - -})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/reservation-page-complete.js b/src/main/webapp/resources/js/event/reservation-page-complete.js deleted file mode 100644 index 3889b72527..0000000000 --- a/src/main/webapp/resources/js/event/reservation-page-complete.js +++ /dev/null @@ -1,154 +0,0 @@ -(function() { - - 'use strict'; - - $(function() { - - $("form").each(function(i, v) { - H5F.setup(v); - }); - - - - - var initListeners = function() { - - $(document).ready(function() { - $(":input:not(input[type=button],input[type=submit],button):visible:first").focus(); - $("select").map(function() { - var value = $(this).attr('value'); - if(value && value.length > 0) { - $(this).val(value); - } - }); - - }); - - $(".update-ticket-owner,.unbind-btn").click(function() { - $($(this).attr('href')).show().find("input:first").focus(); - return false; - }); - - - $(".cancel-update").click(function() { - $('#' + $(this).attr('data-for-form')).hide(); - }); - - - $("[data-dismiss=alert]").click(function() { - $(this).parent().hide('medium'); - }); - - - $('.loading').hide(); - - var activeForms = $('form.show-by-default.not-assigned'); - if(activeForms.length === 0) { - $('#back-to-event-site').removeClass('hidden'); - } - - $('.submit-assignee-data').click(function() { - var frm = $(this.form); - var action = frm.attr('action'); - var uuid = frm.attr('data-ticket-uuid'); - frm.find('.has-error').removeClass('has-error'); - $('#generic-'+uuid+'-error').removeClass('show'); - if (!frm[0].checkValidity()) { - - // Find all invalid fields within the form. - frm.find("input,select,textarea").filter(function(i,v) {return !v.validity.valid;}).each( function( index, node ) { - $(node).parent().addClass('has-error'); - if($(node).parent().parent().hasClass('form-group')) { - $(node).parent().parent().addClass('has-error'); - } - }); - - frm.find("input,select,textarea").filter(function(i,v) {return !v.validity.valid;}).first().focus(); - - return true;//trigger the HTML5 error messages. Thanks to Abraham http://stackoverflow.com/a/11867013 - } - $('#loading-'+uuid).show(); - $('#buttons-bar-'+uuid).hide(); - frm.parents('div[data-ticket-update-page=true]').find('input[name=single-ticket]').val('true'); - jQuery.ajax({ - url: action, - type: 'POST', - data: frm.serialize(), - success: function(result) { - var validationResult = result.validationResult; - if(validationResult.success) { - $('#ticket-detail-'+uuid).replaceWith(result.partial); - $('#ticket-detail-'+uuid).find("select").each(function(index, select) { - var $select = jQuery(select); - var $selectValue = jQuery(select).attr('value'); - if($selectValue && $selectValue.length > 0) { - $select.find("option[value="+$selectValue+"]").attr('selected', 'selected') - } - - }); - initListeners(); - } else { - validationResult.validationErrors.forEach(function(error) { - var element = frm.find('[name='+(error.fieldName.replace('[', '\\[\\\'').replace(']','\\\'\\]'))+']').parents('.form-group'); - if(element.length > 0) { - element.addClass('has-error'); - } else { - $('#generic-'+uuid+'-error').addClass('show'); - } - }); - } - }, - error: function(xhr, textStatus, errorThrown) { - $('#error-'+uuid).addClass('show'); - $('#loading-'+uuid).hide(); - $('#buttons-bar-'+uuid).show(); - }, - complete: function(xhr) { - xhr.done(function() { - $('#error-'+uuid).removeClass('show'); - $('#loading-'+uuid).hide(); - $('#buttons-bar-'+uuid).show(); - }); - } - }); - return false; - }); - - $('.send-ticket-by-email').click(function() { - var frm = $(this.form); - var action = frm.attr('action'); - var uuid = frm.attr('data-ticket-uuid'); - frm.find('.has-error').removeClass('has-error'); - $('#success-'+uuid).removeClass('show'); - $('#error-'+uuid).removeClass('show'); - $('#loading-'+uuid).show(); - jQuery.ajax({ - url: action, - type: 'POST', - data: frm.serialize(), - success: function(result) { - $('#success-'+uuid).removeClass('hidden'); - $('#error-'+uuid).addClass('hidden'); - }, - error: function(xhr, textStatus, errorThrown) { - $('#success-'+uuid).addClass('hidden'); - $('#error-'+uuid).removeClass('hidden'); - }, - complete: function(xhr) { - xhr.done(function() { - $('#loading-'+uuid).hide(); - }); - } - }); - return false; - }); - - }; - - initListeners(); - }); - - - - -})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/reservation-page.js b/src/main/webapp/resources/js/event/reservation-page.js deleted file mode 100644 index 29073b70d2..0000000000 --- a/src/main/webapp/resources/js/event/reservation-page.js +++ /dev/null @@ -1,287 +0,0 @@ -(function() { - - 'use strict'; - - jQuery(function() { - - var hiddenClasses = 'hidden-xs hidden-sm hidden-md hidden-lg'; - - $(document).ready(function() { - $(":input:not(input[type=button],input[type=submit],button):visible:first").focus(); - }); - - H5F.setup(document.getElementById("payment-form")); - - - //validity - //ready for ECMAScript6? - var parser = Number && Number.parseInt ? Number : window; - var validity = new Date(parser.parseInt($("#validity").attr('data-validity'))); - - var displayMessage = function() { - - var validityElem = $("#validity"); - var template = validityElem.attr('data-message'); - - countdown.setLabels( - validityElem.attr('data-labels-singular'), - validityElem.attr('data-labels-plural'), - ' '+validityElem.attr('data-labels-and')+' ', - ', '); - - var timerId = countdown(validity, function(ts) { - if(ts.value < 0) { - validityElem.html(template.replace('##time##', ts.toHTML("strong"))); - } else { - clearInterval(timerId); - $('#validity-container').html(''+validityElem.attr('data-message-time-elapsed')+''); - $('#continue-button').addClass('hidden'); - } - }, countdown.MONTHS|countdown.WEEKS|countdown.DAYS|countdown.HOURS|countdown.MINUTES|countdown.SECONDS); - - }; - - displayMessage(); - - - function submitForm(e) { - var $form = $(this); - - if(!this.checkValidity()) { - return false; - } - - // Disable the submit button to prevent repeated clicks - $form.find('button').prop('disabled', true); - - return true; - } - - - - $("#cancel-reservation").click(function(e) { - var $form = $('#payment-form'); - - $("input[type=submit], button:not([type=button])", $form ).unbind('click'); - $form.unbind('submit'); - $("input", $form).unbind('keypress'); - - $form - .attr('novalidate', 'novalidate') - .unbind('submit', submitForm) - .find('button').prop('disabled', true); - $form.trigger("reset"); - $form.append($('').val(true)) - $form.submit(); - }); - - $('#payment-form').submit(submitForm); - - function markFieldAsError(node) { - $(node).parent().addClass('has-error'); - if($(node).parent().parent().parent().hasClass('form-group')) { - $(node).parent().parent().parent().addClass('has-error'); - } - } - - - // based on http://tjvantoll.com/2012/08/05/html5-form-validation-showing-all-error-messages/ - // http://stackoverflow.com/questions/13798313/set-custom-html5-required-field-validation-message - var createAllErrors = function() { - var form = $(this); - - var showAllErrorMessages = function() { - $(form).find('.has-error').removeClass('has-error'); - // Find all invalid fields within the form. - var invalidFields = form.find("input,select,textarea").filter(function(i,v) {return !v.validity.valid;}).each( function( index, node ) { - markFieldAsError(node); - }); - }; - - // Support Safari - form.on("submit", function( event ) { - if (this.checkValidity && !this.checkValidity() ) { - $(this).find("input,select,textarea").filter(function(i,v) {return !v.validity.valid;}).first().focus(); - event.preventDefault(); - } - }); - - $("input[type=submit], button:not([type=button])", form ).on("click", showAllErrorMessages); - - $("input", form).on("keypress", function(event) { - var type = $(this).attr("type"); - if ( /date|email|month|number|search|tel|text|time|url|week/.test(type) && event.keyCode == 13 ) { - showAllErrorMessages(); - } - }); - }; - - - $('#first-name.autocomplete-src, #last-name.autocomplete-src').change(function() { - fillAttendeeData($('#first-name').val(), $('#last-name').val()); - }); - $('#full-name.autocomplete-src').change(function() { - fillAttendeeData($(this).val()); - }); - $('#email.autocomplete-src').change(function() { - updateIfNotTouched($('#attendeesData').find('.attendee-email').first(), $(this).val()); - }); - - $('#attendeesData').find('.attendee-full-name,.attendee-first-name,.attendee-last-name,.attendee-email').first() - .change(function() { - $(this).removeClass('untouched'); - }); - - $('.copy-from-contact-data').click(function() { - var firstOrFullName = $('#first-name').val() || $('#full-name').val(); - var ticket = $(this).attr('data-ticket'); - fillAttendeeData(firstOrFullName, $('#last-name').val(), ticket); - getTargetTicketData(ticket).find('.attendee-email').first().val($('#email').val()); - }); - - var postponeAssignment = $('#postpone-assignment'); - - postponeAssignment.change(function() { - var element = $('#attendeesData'); - if($(this).is(':checked')) { - element.find('.field-required').attr('required', false); - element.addClass(hiddenClasses) - } else { - element.find('.field-required').attr('required', true); - element.removeClass(hiddenClasses) - } - }); - - if(postponeAssignment.is(':checked')) { - $('#attendeesData').find('.field-required').attr('required', false); - } - - function fillAttendeeData(firstOrFullName, lastName, ticketUUID) { - var useFullName = (typeof lastName === "undefined"); - var element = getTargetTicketData(ticketUUID); - if(useFullName) { - updateIfNotTouched(element.find('.attendee-full-name').first(), firstOrFullName); - } else { - updateIfNotTouched(element.find('.attendee-first-name').first(), firstOrFullName); - updateIfNotTouched(element.find('.attendee-last-name').first(), lastName); - } - } - - function getTargetTicketData(ticketUUID) { - var id = ticketUUID ? '#attendee-data-'+ticketUUID : '#attendeesData'; - return $(id); - } - - function updateIfNotTouched(element, newValue) { - if(element.hasClass('untouched')) { - element.val(newValue); - } - } - - function updateInvoiceFields(radio) { - if(radio.length > 0) { - var elements = $('.invoice-business'); - if(radio.val() === 'true') { - elements.removeClass('hide'); - elements.find('#vatNr').attr('required', true); - } else { - elements.find('input').val('').removeAttr('required'); - elements.addClass('hide'); - } - } - } - - $('input[name=addCompanyBillingDetails]').change(function() { - updateInvoiceFields($(this)); - }); - - updateInvoiceFields($('input[name=addCompanyBillingDetails]:checked')); - - $("select").map(function() { - var value = $(this).attr('value'); - if(value && value.length > 0) { - $(this).val(value); - } - }); - - - $("#invoice-requested").change(function() { - if($("#invoice-requested:checked").length) { - $(".invoice-details-section").removeClass(hiddenClasses); - $("#billingAddressLine1, #billingAddressZip, #billingAddressCity, #vatCountry").attr('required', true) - - } else { - $(".invoice-details-section").addClass(hiddenClasses); - $("#billingAddressLine1, #billingAddressZip, #billingAddressCity, #vatCountry").removeAttr('required'); - } - }); - - - var enabledItalyEInvoicing = $("#italyEInvoicing").length === 1; - - $("#italyEInvoicing").hide(); - - $("#vatCountry").change(function() { - var vatCountryValue = $("#vatCountry").val(); - $("#selected-country-code").text(vatCountryValue); - - if(enabledItalyEInvoicing && vatCountryValue === 'IT') { - $("#italyEInvoicing").show(); - $("#italyEInvoicingFiscalCode").attr('required', true); - } else { - $("#italyEInvoicing").hide(); - $("#italyEInvoicingFiscalCode").removeAttr('required'); - } - }); - - $("input[name=italyEInvoicingReferenceType]").change(function() { - var referenceType = $("input[name=italyEInvoicingReferenceType]:checked").val(); - - $("input[name=italyEInvoicingReferenceAddresseeCode]").attr('disabled', true); - $("input[name=italyEInvoicingReferencePEC]").attr('disabled', true); - - if(referenceType === 'ADDRESSEE_CODE') { - $("input[name=italyEInvoicingReferencePEC]").val('') - $("input[name=italyEInvoicingReferenceAddresseeCode]").removeAttr('disabled'); - } - else if(referenceType === 'PEC') { - $("input[name=italyEInvoicingReferenceAddresseeCode]").val('') - $("input[name=italyEInvoicingReferencePEC]").removeAttr('disabled'); - } - else if(referenceType === 'NONE') { - $("input[name=italyEInvoicingReferenceAddresseeCode]").val('') - $("input[name=italyEInvoicingReferencePEC]").val('') - } - }); - - $("#skip-vat-nr").change(function() { - if($(this).is(':checked')) { - $("#vatNr").removeAttr('required'); - } else { - $("#vatNr").attr('required', true); - } - }); - - // - $("#vatCountry").change(); - $("#invoice-requested").change(); - $("input[name=italyEInvoicingReferenceType]").change(); - - - $("form").each(createAllErrors); - $("input,select,textarea").change(function() { - if( !this.validity.valid) { - $(this).parent().addClass('has-error'); - if($(this).parent().parent().parent().hasClass('form-group')) { - $(this).parent().parent().parent().addClass('has-error'); - } - } else { - $(this).parent().removeClass('has-error'); - if($(this).parent().parent().parent().hasClass('form-group')) { - $(this).parent().parent().parent().removeClass('has-error'); - } - } - }); - - }); -})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/reservation-processing-payment-page.js b/src/main/webapp/resources/js/event/reservation-processing-payment-page.js deleted file mode 100644 index 518d1117e7..0000000000 --- a/src/main/webapp/resources/js/event/reservation-processing-payment-page.js +++ /dev/null @@ -1,30 +0,0 @@ -(function() { - - 'use strict'; - - var element = $('#config-element'); - var url = "/api/events/"+element.attr('data-event-name')+"/reservation/"+element.attr('data-reservation-id')+"/payment/"+element.attr('data-payment-method')+"/status"; - var checkIfPaid = function() { - jQuery.ajax({ - url: url, - type: 'GET', - success: function(result) { - if(result.successful || result.failed) { - clearInterval(handle); - window.location.reload(true); - } - }, - error: function(xhr, textStatus, errorThrown) { - if(console && console.log) { - console.log("error while checking", textStatus); - } - if(xhr.status === 404) { - window.location.reload(true); - } - } - }); - }; - - var handle = setInterval(checkIfPaid, 5000); - -})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/show-event.js b/src/main/webapp/resources/js/event/show-event.js deleted file mode 100644 index 84052a9727..0000000000 --- a/src/main/webapp/resources/js/event/show-event.js +++ /dev/null @@ -1,93 +0,0 @@ -(function() { - - 'use strict'; - - $(function() { - - var restrictedCategories = $('.ticket-category-restricted-true'); - if(restrictedCategories.length === 0) { - $("#promo-code").keydown(function(e) { - if (e.keyCode == 13) { - $("#apply-promo-codes").click(); - return false; - } - }); - - $("#apply-promo-codes").click(function() { - var contextPath = window.location.pathname; - if(!/\/$/.test(contextPath)) { - contextPath = contextPath + '/'; - } - var button = $(this); - var frm = $(this.form); - var promoCodeVal = $("#promo-code").val(); - $('#error-code-not-found').addClass('hidden'); - if(promoCodeVal != null && promoCodeVal.trim() != "") { - jQuery.ajax({ - url: contextPath + 'promoCode/'+encodeURIComponent(promoCodeVal), - type: 'POST', - data: frm.serialize(), - success: function(result) { - if(result.success) { - window.location.reload(); - } else { - $('#error-code-not-found').removeClass('hidden'); - } - } - }); - } - }); - } else { - $('#accessRestrictedTokens').hide(); - //original code: http://stackoverflow.com/a/6677069 - $('html, body').animate({ - scrollTop: restrictedCategories.first().offset().top - }, 500); - } - - $('div.event-description>div.preformatted').shorten({showChars: 250}); - - var collapsible = $('#expiredCategories'); - var showExpiredCategoriesLink = $('a.show-expired-categories'); - collapsible.on('shown.bs.collapse', function() { - showExpiredCategoriesLink.find('i.fa-angle-double-down').removeClass('fa-angle-double-down').addClass('fa-angle-double-up'); - }); - collapsible.on('hidden.bs.collapse', function() { - showExpiredCategoriesLink.find('i.fa-angle-double-up').removeClass('fa-angle-double-up').addClass('fa-angle-double-down'); - }); - $('#collapseOne').on('shown.bs.collapse', function () { - $('#promo-code').focus(); - }) - - function countTotalTicketSelected() { - var selected = $("[data-ticket-selector]").map(function() { - return parseInt(this.value); - }); - var tot = 0; - for(var i = 0; i < selected.length; i++) { - tot+=selected[i]; - } - return tot; - } - - function updateSelect(select, totalSelected) { - var maxAmount = parseInt($(select).attr('data-max-amount-per-ticket')) * totalSelected; - var options = []; - for(var i = 0; i <= maxAmount; i++) { - options.push($("")); - } - var value = select.value; - $(select).empty(); - $(select).append(options); - select.value = Math.min(value, maxAmount); - } - - $("[data-ticket-selector]").change(function() { - var totalSelected = countTotalTicketSelected(); - $("[data-max-amount-per-ticket]").each(function() { - updateSelect(this, totalSelected); - }); - }); - }); - -})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/show-ticket.js b/src/main/webapp/resources/js/event/show-ticket.js deleted file mode 100644 index 96f1b94565..0000000000 --- a/src/main/webapp/resources/js/event/show-ticket.js +++ /dev/null @@ -1,11 +0,0 @@ -(function() { - - 'use strict'; - - - $(function() { - $("[data-dismiss=alert]").click(function() { - $(this).parent().hide('medium'); - }); - }); -})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/update-ticket.js b/src/main/webapp/resources/js/event/update-ticket.js deleted file mode 100644 index b9712b5ac5..0000000000 --- a/src/main/webapp/resources/js/event/update-ticket.js +++ /dev/null @@ -1,6 +0,0 @@ -(function() { - 'use strict'; - $(function() { - $('.collapsible-container[data-ticket-update-page="true"] form[data-ticket-uuid]').removeClass('hide-by-default'); - }); -})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/event/waiting-queue.js b/src/main/webapp/resources/js/event/waiting-queue.js deleted file mode 100644 index fbef018e8d..0000000000 --- a/src/main/webapp/resources/js/event/waiting-queue.js +++ /dev/null @@ -1,65 +0,0 @@ -(function() { - "use strict"; - - $('#waiting-queue-subscribe').click(function() { - var frm = $(this.form); - var action = frm.attr('action'); - var uuid = frm.attr('data-ticket-uuid'); - frm.find('.has-error').removeClass('has-error'); - frm.find('#generic-error').removeClass('show'); - if (!frm[0].checkValidity()) { - // Find all invalid fields within the form. - frm.find("input,select,textarea").filter(function(i,v) {return !v.validity.valid;}).each( function( index, node ) { - $(node).parent().addClass('has-error'); - if($(node).parent().parent().hasClass('form-group')) { - $(node).parent().parent().addClass('has-error'); - } - }); - frm.find("input,select,textarea").filter(function(i,v) {return !v.validity.valid;}).first().focus(); - return true;//trigger the HTML5 error messages. Thanks to Abraham http://stackoverflow.com/a/11867013 - } - $('#loading').addClass('show'); - $('#waiting-queue-subscribe').attr('disabled', true); - jQuery.ajax({ - url: action, - type: 'POST', - data: frm.serialize(), - success: function(result) { - var validationResult = result.validationResult; - if(validationResult.success) { - $('#waiting-queue-subscription').replaceWith(result.partial); - } else { - validationResult.validationErrors.forEach(function(error) { - var element = frm.find('[name='+error.fieldName+']').parents('label'); - if(element.length > 0) { - element.addClass('has-error'); - } else { - $('#generic-error').addClass('show'); - } - }); - } - }, - error: function(xhr, textStatus, errorThrown) { - frm.find('#generic-error').addClass('show'); - $('#loading').removeClass('show'); - $('#waiting-queue-subscribe').attr('disabled', false); - }, - complete: function(xhr) { - xhr.done(function() { - frm.find('#generic-error').removeClass('show'); - $('#loading').removeClass('show'); - $('#waiting-queue-subscribe').attr('disabled', false); - }); - } - }); - return false; - }); - - - $("select").map(function() { - var value = $(this).attr('value'); - if(value && value.length > 0) { - $(this).val(value); - } - }); -})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/h5f/h5f.min.js b/src/main/webapp/resources/js/h5f/h5f.min.js deleted file mode 100644 index f001ef5717..0000000000 --- a/src/main/webapp/resources/js/h5f/h5f.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! H5F -* https://github.com/ryanseddon/H5F/ -* Copyright (c) Ryan Seddon | Licensed MIT */ -(function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof module&&module.exports?module.exports=t():e.H5F=t()})(this,function(){var e,t,a,i,n,r,l,s,o,u,d,c,v,p,f,m,b,h,g,y,w,C,N,A,E,$,x=document,k=x.createElement("input"),q=/^[a-zA-Z0-9.!#$%&'*+-\/=?\^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,M=/[a-z][\-\.+a-z]*:\/\//i,L=/^(input|select|textarea)$/i;return r=function(e,t){var a=!e.nodeType||!1,i={validClass:"valid",invalidClass:"error",requiredClass:"required",placeholderClass:"placeholder",onSubmit:Function.prototype,onInvalid:Function.prototype};if("object"==typeof t)for(var r in i)t[r]===void 0&&(t[r]=i[r]);if(n=t||i,a)for(var s=0,o=e.length;o>s;s++)l(e[s]);else l(e)},l=function(a){var i,r=a.elements,l=r.length,c=!!a.attributes.novalidate;if(g(a,"invalid",o,!0),g(a,"blur",o,!0),g(a,"input",o,!0),g(a,"keyup",o,!0),g(a,"focus",o,!0),g(a,"change",o,!0),g(a,"click",u,!0),g(a,"submit",function(i){return e=!0,t||c||a.checkValidity()?(n.onSubmit.call(a,i),void 0):(w(i),void 0)},!1),!v())for(a.checkValidity=function(){return d(a)};l--;)i=!!r[l].attributes.required,"fieldset"!==r[l].nodeName.toLowerCase()&&s(r[l])},s=function(e){var t=e,a=h(t),n={type:t.getAttribute("type"),pattern:t.getAttribute("pattern"),placeholder:t.getAttribute("placeholder")},r=/^(email|url)$/i,l=/^(input|keyup)$/i,s=r.test(n.type)?n.type:n.pattern?n.pattern:!1,o=p(t,s),u=m(t,"step"),v=m(t,"min"),b=m(t,"max"),g=!(""===t.validationMessage||void 0===t.validationMessage);t.checkValidity=function(){return d.call(this,t)},t.setCustomValidity=function(e){c.call(t,e)},t.validity={valueMissing:a,patternMismatch:o,rangeUnderflow:v,rangeOverflow:b,stepMismatch:u,customError:g,valid:!(a||o||u||v||b||g)},n.placeholder&&!l.test(i)&&f(t)},o=function(e){var t=C(e)||e,a=/^(input|keyup|focusin|focus|change)$/i,r=/^(submit|image|button|reset)$/i,l=/^(checkbox|radio)$/i,u=!0;!L.test(t.nodeName)||r.test(t.type)||r.test(t.nodeName)||(i=e.type,v()||s(t),t.validity.valid&&(""!==t.value||l.test(t.type))||t.value!==t.getAttribute("placeholder")&&t.validity.valid?(A(t,[n.invalidClass,n.requiredClass]),N(t,n.validClass)):a.test(i)?t.validity.valueMissing&&A(t,[n.requiredClass,n.invalidClass,n.validClass]):t.validity.valueMissing?(A(t,[n.invalidClass,n.validClass]),N(t,n.requiredClass)):t.validity.valid||(A(t,[n.validClass,n.requiredClass]),N(t,n.invalidClass)),"input"===i&&u&&(y(t.form,"keyup",o,!0),u=!1))},d=function(t){var a,i,r,l,s,u=!1;if("form"===t.nodeName.toLowerCase()){a=t.elements;for(var d=0,c=a.length;c>d;d++)i=a[d],r=!!i.attributes.disabled,l=!!i.attributes.required,s=!!i.attributes.pattern,"fieldset"!==i.nodeName.toLowerCase()&&!r&&(l||s&&l)&&(o(i),i.validity.valid||u||(e&&i.focus(),u=!0,n.onInvalid.call(t,i)));return!u}return o(t),t.validity.valid},c=function(e){var t=this;t.validationMessage=e},u=function(e){var a=C(e);a.attributes.formnovalidate&&"submit"===a.type&&(t=!0)},v=function(){return E(k,"validity")&&E(k,"checkValidity")},p=function(e,t){if("email"===t)return!q.test(e.value);if("url"===t)return!M.test(e.value);if(t){var i=e.getAttribute("placeholder"),n=e.value;return a=RegExp("^(?:"+t+")$"),n===i?!1:""===n?!1:!a.test(e.value)}return!1},f=function(e){var t={placeholder:e.getAttribute("placeholder")},a=/^(focus|focusin|submit)$/i,r=/^(input|textarea)$/i,l=/^password$/i,s=!!("placeholder"in k);s||!r.test(e.nodeName)||l.test(e.type)||(""!==e.value||a.test(i)?e.value===t.placeholder&&a.test(i)&&(e.value="",A(e,n.placeholderClass)):(e.value=t.placeholder,g(e.form,"submit",function(){i="submit",f(e)},!0),N(e,n.placeholderClass)))},m=function(e,t){var a=parseInt(e.getAttribute("min"),10)||0,i=parseInt(e.getAttribute("max"),10)||!1,n=parseInt(e.getAttribute("step"),10)||1,r=parseInt(e.value,10),l=(r-a)%n;return h(e)||isNaN(r)?"number"===e.getAttribute("type")?!0:!1:"step"===t?e.getAttribute("step")?0!==l:!1:"min"===t?e.getAttribute("min")?a>r:!1:"max"===t?e.getAttribute("max")?r>i:!1:void 0},b=function(e){var t=!!e.attributes.required;return t?h(e):!1},h=function(e){var t=e.getAttribute("placeholder"),a=/^(checkbox|radio)$/i,i=!!e.attributes.required;return!(!i||""!==e.value&&e.value!==t&&(!a.test(e.type)||$(e)))},g=function(e,t,a,i){E(window,"addEventListener")?e.addEventListener(t,a,i):E(window,"attachEvent")&&window.event!==void 0&&("blur"===t?t="focusout":"focus"===t&&(t="focusin"),e.attachEvent("on"+t,a))},y=function(e,t,a,i){E(window,"removeEventListener")?e.removeEventListener(t,a,i):E(window,"detachEvent")&&window.event!==void 0&&e.detachEvent("on"+t,a)},w=function(e){e=e||window.event,e.stopPropagation&&e.preventDefault?(e.stopPropagation(),e.preventDefault()):(e.cancelBubble=!0,e.returnValue=!1)},C=function(e){return e=e||window.event,e.target||e.srcElement},N=function(e,t){var a;e.className?(a=RegExp("(^|\\s)"+t+"(\\s|$)"),a.test(e.className)||(e.className+=" "+t)):e.className=t},A=function(e,t){var a,i,n="object"==typeof t?t.length:1,r=n;if(e.className)if(e.className===t)e.className="";else for(;n--;)a=RegExp("(^|\\s)"+(r>1?t[n]:t)+"(\\s|$)"),i=e.className.match(a),i&&3===i.length&&(e.className=e.className.replace(a,i[1]&&i[2]?" ":""))},E=function(e,t){var a=typeof e[t],i=RegExp("^function|object$","i");return!!(i.test(a)&&e[t]||"unknown"===a)},$=function(e){for(var t=document.getElementsByName(e.name),a=0;t.length>a;a++)if(t[a].checked)return!0;return!1},{setup:r}}); \ No newline at end of file diff --git a/src/main/webapp/resources/js/payment/creditcard-embedded-stripe.js b/src/main/webapp/resources/js/payment/creditcard-embedded-stripe.js deleted file mode 100644 index 01a18d9451..0000000000 --- a/src/main/webapp/resources/js/payment/creditcard-embedded-stripe.js +++ /dev/null @@ -1,154 +0,0 @@ -(function(w, doc) { - - 'use strict'; - - var style = { - base: { - color: '#000000', - lineHeight: '18px', - fontFamily: '"Helvetica Neue", Helvetica, sans-serif', - fontSmoothing: 'antialiased', - fontSize: '14px', - '::placeholder': { - color: '#aab7c4' - } - }, - invalid: { - color: '#a94442', - iconColor: '#a94442' - } - }; - - var setup = function() { - if(w.alfio && w.alfio.registerPaymentHandler) { - var stripeHandler; - var card; - var stripeEl = doc.getElementById("stripe-key"); - var eventName = stripeEl.getAttribute("data-stripe-event-name"); - var reservationId = stripeEl.getAttribute("data-stripe-reservation-id"); - var readyToGo = false; - - w.alfio.registerPaymentHandler({ - paymentMethod: 'CREDIT_CARD', - id: 'STRIPE', - pay: function(confirmHandler, cancelHandler) { - retrieveSecretKey(eventName, reservationId, function(secret) { - stripeHandler.handleCardPayment(secret, card, getMetadata(stripeEl)).then(function(result) { - if(result.error) { - cancelHandler(result.error.message); - } else { - var checkIfPaid = function() { - var url = "/api/events/"+eventName+"/reservation/"+reservationId+"/payment/CREDIT_CARD/status"; - jQuery.ajax({ - url: url, - type: 'GET', - success: function(result) { - if(result.successful) { - var $form = $('#payment-form'); - $form.append($('').val(result.gatewayIdOrNull)); - clearInterval(handle); - confirmHandler(true); - } - if(result.failed) { - confirmHandler(false); - } - }, - error: function(xhr, textStatus, errorThrown) { - errorCallback(textStatus); - } - }); - }; - var handle = setInterval(checkIfPaid, 1000); - } - }); - }, cancelHandler); - }, - init: function() { - var options = {}; - var connectedAccount = stripeEl.getAttribute('data-stripe-on-behalf-of'); - if(connectedAccount && connectedAccount !== '') { - options.stripeAccount = connectedAccount; - } - stripeHandler = Stripe(stripeEl.getAttribute('data-stripe-key'), options); - - card = stripeHandler.elements({locale: $('html').attr('lang')}).create('card', {style: style}); - card.mount('#card-element'); - card.addEventListener('change', function(event) { - readyToGo = false; - var displayError = $('#card-errors'); - var cardContainer = $('#card-element-container'); - if (event.error) { - cardContainer.addClass('has-error'); - displayError.removeClass('hide'); - displayError.find('#error-message').text(event.error.message); - } else { - cardContainer.removeClass('has-error'); - displayError.addClass('hide'); - displayError.find('#error-message').text(''); - } - if(event.complete) { - readyToGo = true; - } - }); - }, - valid: function() { - return readyToGo; - }, - active: function() { - var attr; - var stripe = doc.getElementById("stripe-key"); - return stripe != null && (attr = stripe.attributes.getNamedItem("data-stripe-embedded")) != null && attr.value === 'true'; - } - }); - } else { - w.setTimeout(setup, 50); - } - }; - - - setup(); - - var getMetadata = function(stripeEl) { - var nullIfEmpty = function(val) { - if(val == null || val === '') { - return null; - } - return val; - }; - return { - payment_method_data: { - billing_details: { - name: stripeEl.getAttribute('data-stripe-contact-name'), - email: stripeEl.getAttribute('data-stripe-email'), - address: { - line1: nullIfEmpty(stripeEl.getAttribute('data-stripe-contact-address')), - postal_code: nullIfEmpty(stripeEl.getAttribute('data-stripe-contact-zip')), - country: nullIfEmpty(stripeEl.getAttribute('data-stripe-contact-country').toLowerCase()) - } - } - } - }; - }; - - var retrieveSecretKey = function(eventName, reservationId, successCallback, errorCallback) { - var url = "/api/events/"+eventName+"/reservation/"+reservationId+"/payment/CREDIT_CARD/init"; - jQuery.ajax({ - url: url, - type: 'POST', - data: { - '_csrf': document.forms[0].elements['_csrf'].value - }, - success: function(result) { - if(result.errorMessage || !result.clientSecret) { - errorCallback(result.errorMessage); - } else { - successCallback(result.clientSecret); - } - }, - error: function(xhr, textStatus, errorThrown) { - errorCallback(textStatus); - } - }); - } - -})(window, document); \ No newline at end of file diff --git a/src/main/webapp/resources/js/payment/creditcard-stripe.js b/src/main/webapp/resources/js/payment/creditcard-stripe.js deleted file mode 100644 index 2cc6fc70fd..0000000000 --- a/src/main/webapp/resources/js/payment/creditcard-stripe.js +++ /dev/null @@ -1,65 +0,0 @@ -(function(w, doc) { - - 'use strict'; - - var setup = function() { - if(w.alfio && w.alfio.registerPaymentHandler) { - var stripeHandler; - var confirmFn; - var closeFn; - var stripeEl = doc.getElementById("stripe-key"); - - w.alfio.registerPaymentHandler({ - paymentMethod: 'CREDIT_CARD', - id: 'STRIPE', - pay: function(confirmHandler, cancelHandler) { - stripeHandler.open({ - name: stripeEl.getAttribute('data-stripe-contact-name'), - description: stripeEl.getAttribute('data-stripe-description'), - zipCode: false, - allowRememberMe: false, - amount: parseInt(stripeEl.getAttribute('data-price') || '0', 10), - currency: stripeEl.getAttribute('data-currency'), - email: stripeEl.getAttribute('data-stripe-email') - }); - confirmFn = confirmHandler; - closeFn = cancelHandler; - }, - init: function() { - var confirmCalled = false; - stripeHandler = StripeCheckout.configure({ - key: stripeEl.getAttribute('data-stripe-key'), - image: doc.getElementById("event-logo").getAttribute('src'), - locale: $('html').attr('lang'), - token: function(token) { - var $form = $('#payment-form'); - $form.append($('').val(token.id)); - confirmFn(true); - confirmCalled = true; - }, - closed: function() { - if(closeFn && !confirmCalled) { - closeFn(); - } - } - }); - }, - valid: function() { - return true; - }, - active: function() { - var attr; - var stripe = doc.getElementById("stripe-key"); - //checkout is the default - return stripe != null && (attr = stripe.attributes.getNamedItem("data-stripe-embedded")) == null || attr.value === 'false'; - } - }); - } else { - w.setTimeout(setup, 50); - } - }; - - - setup(); - -})(window, document); \ No newline at end of file diff --git a/src/main/webapp/resources/js/payment/free-of-charge.js b/src/main/webapp/resources/js/payment/free-of-charge.js deleted file mode 100644 index b9893bd69d..0000000000 --- a/src/main/webapp/resources/js/payment/free-of-charge.js +++ /dev/null @@ -1,30 +0,0 @@ -(function(w) { - - 'use strict'; - - var setup = function() { - if(w.alfio && w.alfio.registerPaymentHandler) { - w.alfio.registerPaymentHandler({ - paymentMethod: 'NONE', - id: 'NONE', - pay: function(confirmHandler) { - confirmHandler(true); - }, - init: function() { - }, - valid: function() { - return true; - }, - active: function() { - return true; - } - }); - } else { - w.setTimeout(setup, 50); - } - }; - - - setup(); - -})(window, document); \ No newline at end of file diff --git a/src/main/webapp/resources/js/payment/offline.js b/src/main/webapp/resources/js/payment/offline.js deleted file mode 100644 index fb7c6bfd0b..0000000000 --- a/src/main/webapp/resources/js/payment/offline.js +++ /dev/null @@ -1,30 +0,0 @@ -(function(w) { - - 'use strict'; - - var setup = function() { - if(w.alfio && w.alfio.registerPaymentHandler) { - w.alfio.registerPaymentHandler({ - paymentMethod: 'BANK_TRANSFER', - id: 'OFFLINE', - pay: function(confirmHandler) { - confirmHandler(true); - }, - init: function() { - }, - valid: function() { - return true; - }, - active: function() { - return true; - } - }); - } else { - w.setTimeout(setup, 50); - } - }; - - - setup(); - -})(window, document); \ No newline at end of file diff --git a/src/main/webapp/resources/js/payment/on-site.js b/src/main/webapp/resources/js/payment/on-site.js deleted file mode 100644 index 7abfefad91..0000000000 --- a/src/main/webapp/resources/js/payment/on-site.js +++ /dev/null @@ -1,30 +0,0 @@ -(function(w) { - - 'use strict'; - - var setup = function() { - if(w.alfio && w.alfio.registerPaymentHandler) { - w.alfio.registerPaymentHandler({ - paymentMethod: 'ON_SITE', - id: 'ON_SITE', - pay: function(confirmHandler) { - confirmHandler(true); - }, - init: function() { - }, - valid: function() { - return true; - }, - active: function() { - return true; - } - }); - } else { - w.setTimeout(setup, 50); - } - }; - - - setup(); - -})(window, document); \ No newline at end of file diff --git a/src/main/webapp/resources/js/payment/paypal.js b/src/main/webapp/resources/js/payment/paypal.js deleted file mode 100644 index 8b4785e761..0000000000 --- a/src/main/webapp/resources/js/payment/paypal.js +++ /dev/null @@ -1,30 +0,0 @@ -(function(w) { - - 'use strict'; - - var setup = function() { - if(w.alfio && w.alfio.registerPaymentHandler) { - w.alfio.registerPaymentHandler({ - paymentMethod: 'PAYPAL', - id: 'PAYPAL', - pay: function(confirmHandler) { - confirmHandler(true); - }, - init: function() { - }, - valid: function() { - return true; - }, - active: function() { - return true; - } - }); - } else { - w.setTimeout(setup, 50); - } - }; - - - setup(); - -})(window, document); \ No newline at end of file diff --git a/src/test/java/alfio/controller/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/ReservationFlowIntegrationTest.java deleted file mode 100644 index 335ed14ebe..0000000000 --- a/src/test/java/alfio/controller/ReservationFlowIntegrationTest.java +++ /dev/null @@ -1,626 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller; - -import alfio.TestConfiguration; -import alfio.config.DataSourceConfiguration; -import alfio.config.Initializer; -import alfio.controller.api.AttendeeApiController; -import alfio.controller.api.ReservationApiController; -import alfio.controller.api.admin.CheckInApiController; -import alfio.controller.api.admin.EventApiController; -import alfio.controller.api.admin.SerializablePair; -import alfio.controller.api.admin.UsersApiController; -import alfio.controller.api.support.TicketHelper; -import alfio.controller.form.ContactAndTicketsForm; -import alfio.controller.form.PaymentForm; -import alfio.controller.form.ReservationForm; -import alfio.controller.form.UpdateTicketOwnerForm; -import alfio.controller.support.TicketDecorator; -import alfio.manager.*; -import alfio.manager.i18n.I18nManager; -import alfio.manager.support.CheckInStatus; -import alfio.manager.support.TicketAndCheckInResult; -import alfio.manager.system.ConfigurationManager; -import alfio.manager.user.UserManager; -import alfio.model.*; -import alfio.model.audit.ScanAudit; -import alfio.model.modification.DateTimeModification; -import alfio.model.modification.TicketCategoryModification; -import alfio.model.modification.TicketReservationModification; -import alfio.model.modification.UserModification; -import alfio.model.result.ValidationResult; -import alfio.model.system.ConfigurationKeys; -import alfio.model.transaction.PaymentProxy; -import alfio.model.user.User; -import alfio.repository.EventRepository; -import alfio.repository.TicketCategoryRepository; -import alfio.repository.TicketReservationRepository; -import alfio.repository.audit.ScanAuditRepository; -import alfio.repository.system.ConfigurationRepository; -import alfio.repository.user.OrganizationRepository; -import alfio.test.util.IntegrationTestUtil; -import alfio.util.BaseIntegrationTest; -import alfio.util.EventUtil; -import alfio.util.Json; -import alfio.util.TemplateManager; -import com.fasterxml.jackson.core.type.TypeReference; -import com.opencsv.CSVReader; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang3.time.DateUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.ui.Model; -import org.springframework.validation.BeanPropertyBindingResult; -import org.springframework.validation.BindingResult; -import org.springframework.validation.support.BindingAwareModelMap; -import org.springframework.web.context.request.ServletWebRequest; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; - -import java.io.IOException; -import java.io.StringReader; -import java.math.BigDecimal; -import java.security.Principal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.ZonedDateTime; -import java.util.*; - -import static alfio.test.util.IntegrationTestUtil.AVAILABLE_SEATS; -import static alfio.test.util.IntegrationTestUtil.initEvent; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; - -/** - * - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ReservationFlowIntegrationTest.ControllerConfiguration.class}) -@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class ReservationFlowIntegrationTest extends BaseIntegrationTest { - - - @Configuration - @ComponentScan(basePackages = {"alfio.controller"}) - public static class ControllerConfiguration { - - } - - private static final Map DESCRIPTION = Collections.singletonMap("en", "desc"); - - @Autowired - private ConfigurationRepository configurationRepository; - - @Autowired - private OrganizationRepository organizationRepository; - - @Autowired - private UserManager userManager; - - @Autowired - private EventManager eventManager; - - @Autowired - private EventRepository eventRepository; - - @Autowired - private EventController eventController; - - @Autowired - private EventStatisticsManager eventStatisticsManager; - - @Autowired - private ReservationController reservationController; - - @Autowired - private TicketController ticketController; - - @Autowired - private EventApiController eventApiController; - - @Autowired - private CheckInApiController checkInApiController; - - @Autowired - private TicketHelper ticketHelper; - @Autowired - private I18nManager i18nManager; - @Autowired - private TicketReservationRepository ticketReservationRepository; - @Autowired - private ScanAuditRepository scanAuditRepository; - @Autowired - private TicketReservationManager ticketReservationManager; - @Autowired - private ExtensionManager extensionManager; - - @Autowired - private TicketCategoryRepository ticketCategoryRepository; - - @Autowired - private CheckInManager checkInManager; - - @Autowired - private AttendeeApiController attendeeApiController; - - @Autowired - private UsersApiController usersApiController; - - @Autowired - private NotificationManager notificationManager; - - @Autowired - private FileUploadManager fileUploadManager; - - @Autowired - private TemplateManager templateManager; - - @Autowired - private ConfigurationManager configurationManager; - - private ReservationApiController reservationApiController; - private InvoiceReceiptController invoiceReceiptController; - - - - private Event event; - private String user; - - @Before - public void ensureConfiguration() { - - IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository); - List categories = Collections.singletonList( - new TicketCategoryModification(null, "default", AVAILABLE_SEATS, - new DateTimeModification(LocalDate.now().minusDays(1), LocalTime.now()), - new DateTimeModification(LocalDate.now().plusDays(1), LocalTime.now()), - DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null)); - Pair eventAndUser = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); - - event = eventAndUser.getKey(); - user = eventAndUser.getValue() + "_owner"; - - // - - reservationApiController = new ReservationApiController(ticketHelper, mock(TemplateManager.class), i18nManager); - invoiceReceiptController = new InvoiceReceiptController(eventRepository, ticketReservationManager, fileUploadManager, templateManager, configurationManager, extensionManager); - - //promo code at event level - eventManager.addPromoCode(PROMO_CODE, event.getId(), null, ZonedDateTime.now().minusDays(2), event.getEnd().plusDays(2), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "test@test.ch", PromoCodeDiscount.CodeType.DISCOUNT, null); - } - - private static final String PROMO_CODE = "MYPROMOCODE"; - - - /** - * Test a complete offline payment flow. - * Will not check in detail... - */ - @Test - public void reservationFlowTest() throws Exception{ - - String eventName = event.getShortName(); - - assertTrue(checkInManager.findAllFullTicketInfo(event.getId()).isEmpty()); - - List eventStatistic = eventStatisticsManager.getAllEventsWithStatistics(user); - assertEquals(1, eventStatistic.size()); - assertTrue(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 1)).isEmpty()); - EventWithAdditionalInfo eventWithAdditionalInfo = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user); - assertEquals(0, eventWithAdditionalInfo.getNotSoldTickets()); - assertEquals(0, eventWithAdditionalInfo.getSoldTickets()); - assertEquals(20, eventWithAdditionalInfo.getAvailableSeats()); - - - eventManager.toggleActiveFlag(event.getId(), user, true); - // list events - String eventList = eventController.listEvents(new BindingAwareModelMap(), Locale.ENGLISH); - if(eventManager.getPublishedEvents().size() == 1) { - Assert.assertTrue(eventList.startsWith("redirect:/")); - } else { - assertEquals("/event/event-list", eventList); - } - // - - // show event - String showEvent = eventController.showEvent(eventName, new BindingAwareModelMap(), new MockHttpServletRequest(), Locale.ENGLISH); - assertEquals("/event/show-event", showEvent); - // - - // check calendar - checkCalendar(eventName); - // - - String redirectResult = reserveTicket(eventName); - String redirectStart = "redirect:/event/" + eventName + "/reservation/"; - // check reservation success - Assert.assertTrue(redirectResult.startsWith(redirectStart)); - Assert.assertTrue(redirectResult.endsWith("/book")); - // - - - String reservationIdentifier = redirectResult.substring(redirectStart.length()).replace("/book", ""); - - - // check that the booking page is shown - String bookingPage = reservationController.showBookingPage(eventName, reservationIdentifier, new BindingAwareModelMap(), Locale.ENGLISH); - assertEquals("/event/reservation-page", bookingPage); - // - - // pay offline - String successPage = payOffline(eventName, reservationIdentifier); - assertEquals("redirect:/event/" + eventName + "/reservation/" + reservationIdentifier + "/success", successPage); - // - - //go to success page, payment is still pending - String confirmationPage = reservationController.showConfirmationPage(eventName, reservationIdentifier, false, false, new BindingAwareModelMap(), Locale.ENGLISH, new MockHttpServletRequest()); - Assert.assertTrue(confirmationPage.endsWith("/waitingPayment")); - - - assertEquals("/event/reservation-waiting-for-payment", reservationController.showWaitingPaymentPage(eventName, reservationIdentifier, new BindingAwareModelMap(), Locale.ENGLISH)); - - // - validatePayment(eventName, reservationIdentifier); - // - - Assert.assertTrue(reservationController.showWaitingPaymentPage(eventName, reservationIdentifier, new BindingAwareModelMap(), Locale.ENGLISH).endsWith("/success")); - - //check receipt/invoice - MockHttpServletResponse responseForReceipt = new MockHttpServletResponse(); - // no invoice - - Authentication anon = new AnonymousAuthenticationToken("key", "anonymous", - AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - - Assert.assertEquals(404, invoiceReceiptController.getInvoice(eventName, reservationIdentifier, new MockHttpServletResponse(), anon).getStatusCodeValue()); - // we got a receipt - Assert.assertEquals(200, invoiceReceiptController.getReceipt(eventName, reservationIdentifier, responseForReceipt, anon).getStatusCodeValue()); - Assert.assertEquals("attachment; filename=\"receipt-" + eventName + "-" + reservationIdentifier + ".pdf\"", responseForReceipt.getHeader("Content-Disposition")); - // - - // - TicketDecorator ticketDecorator = checkReservationComplete(eventName, reservationIdentifier); - // - - - - - String ticketIdentifier = ticketDecorator.getUuid(); - - - //ticket is still not assigned, will redirect - Assert.assertTrue(ticketController.showTicket(eventName, ticketIdentifier, false, Locale.ENGLISH, new BindingAwareModelMap()).startsWith("redirect:/event/")); - Assert.assertTrue(ticketController.showTicketForUpdate(eventName, ticketIdentifier, new BindingAwareModelMap(), Locale.ENGLISH).startsWith("redirect:/event/")); - // - - String fname1 = "Test"; - String lname1 = "McTest"; - - //assign ticket to person - assignTicket(eventName, reservationIdentifier, ticketIdentifier, fname1, lname1); - - assertEquals(1, checkInManager.findAllFullTicketInfo(event.getId()).size()); - - assertEquals("/event/update-ticket", ticketController.showTicketForUpdate(eventName, ticketIdentifier, new BindingAwareModelMap(), Locale.ENGLISH)); - - // - assertEquals("/event/show-ticket", ticketController.showTicket(eventName, ticketIdentifier, false, Locale.ENGLISH, new BindingAwareModelMap())); - - //send email - assertEquals("OK", ticketController.sendTicketByEmail(eventName, ticketIdentifier, new MockHttpServletRequest())); - assertTrue(notificationManager.sendWaitingMessages() > 0); //more than 0 emails should be sent (4 in theory) - // - //download ticket - MockHttpServletResponse responseForDownloadTicket = new MockHttpServletResponse(); - ticketController.generateTicketPdf(eventName, ticketIdentifier, new MockHttpServletRequest(), responseForDownloadTicket); - assertEquals("attachment; filename=ticket-" + ticketIdentifier + ".pdf", responseForDownloadTicket.getHeader("Content-Disposition")); - // - //generate qrcode png - MockHttpServletResponse responseForTicketCode = new MockHttpServletResponse(); - ticketController.generateTicketCode(eventName, ticketIdentifier, responseForTicketCode); - assertEquals("image/png", responseForTicketCode.getContentType()); - // - checkCSV(eventName, ticketIdentifier, fname1 + " " + lname1); - - - // use api to update - UpdateTicketOwnerForm updateTicketOwnerForm = new UpdateTicketOwnerForm(); - updateTicketOwnerForm.setFirstName("Test"); - updateTicketOwnerForm.setLastName("Testson"); - updateTicketOwnerForm.setEmail("testmctest@test.com"); - updateTicketOwnerForm.setUserLanguage("en"); - reservationApiController.assignTicketToPerson(eventName, ticketIdentifier, true, - updateTicketOwnerForm, new BeanPropertyBindingResult(updateTicketOwnerForm, "updateTicketForm"), new MockHttpServletRequest(), new BindingAwareModelMap(), - null); - checkCSV(eventName, ticketIdentifier, "Test Testson"); - // - - //update - String fname2 = "Test"; - String lname2 = "OTest"; - assignTicket(eventName, reservationIdentifier, ticketIdentifier, fname2, lname2); - checkCSV(eventName, ticketIdentifier, fname2 + " " + lname2); - - //lock ticket - Principal principal = mock(Principal.class); - Mockito.when(principal.getName()).thenReturn(user); - eventApiController.toggleTicketLocking(eventName, ticketDecorator.getCategoryId(), ticketDecorator.getId(), principal); - - assignTicket(eventName, reservationIdentifier, ticketIdentifier, fname1, fname2); - checkCSV(eventName, ticketIdentifier, fname2 + " " + lname2); - - //ticket has changed, update - ticketDecorator = checkReservationComplete(eventName, reservationIdentifier); - - - // check stats after selling one ticket - assertFalse(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 2)).isEmpty()); - EventWithAdditionalInfo eventWithAdditionalInfo2 = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user); - assertEquals(0, eventWithAdditionalInfo2.getNotSoldTickets()); - assertEquals(1, eventWithAdditionalInfo2.getSoldTickets()); - assertEquals(20, eventWithAdditionalInfo2.getAvailableSeats()); - assertEquals(0, eventWithAdditionalInfo2.getCheckedInTickets()); - - - //--- check in sequence - String ticketCode = ticketDecorator.ticketCode(event.getPrivateKey()); - TicketAndCheckInResult ticketAndCheckInResult = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode); - assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult.getResult().getStatus()); - CheckInApiController.TicketCode tc = new CheckInApiController.TicketCode(); - tc.setCode(ticketCode); - assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(event.getId(), ticketIdentifier, tc, new TestingAuthenticationToken("ciccio","ciccio")).getResult().getStatus()); - List audits = scanAuditRepository.findAllForEvent(event.getId()); - assertFalse(audits.isEmpty()); - assertTrue(audits.stream().anyMatch(sa -> sa.getTicketUuid().equals(ticketIdentifier))); - - - TicketAndCheckInResult ticketAndCheckInResultOk = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode); - assertEquals(CheckInStatus.ALREADY_CHECK_IN, ticketAndCheckInResultOk.getResult().getStatus()); - - // check stats after check in one ticket - assertFalse(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 1)).isEmpty()); - EventWithAdditionalInfo eventWithAdditionalInfo3 = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user); - assertEquals(0, eventWithAdditionalInfo3.getNotSoldTickets()); - assertEquals(0, eventWithAdditionalInfo3.getSoldTickets()); - assertEquals(20, eventWithAdditionalInfo3.getAvailableSeats()); - assertEquals(1, eventWithAdditionalInfo3.getCheckedInTickets()); - - - - //test revert check in - assertTrue(checkInApiController.revertCheckIn(event.getId(), ticketIdentifier, principal)); - assertFalse(checkInApiController.revertCheckIn(event.getId(), ticketIdentifier, principal)); - TicketAndCheckInResult ticketAndCheckInResult2 = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode); - assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult2.getResult().getStatus()); - - UsersApiController.UserWithPasswordAndQRCode sponsorUser = usersApiController.insertUser(new UserModification(null, event.getOrganizationId(), "SPONSOR", "sponsor", "first", "last", "email@email.com", User.Type.INTERNAL, null, null), "http://localhost:8080", principal); - Principal sponsorPrincipal = mock(Principal.class); - Mockito.when(sponsorPrincipal.getName()).thenReturn(sponsorUser.getUsername()); - - // check failures - assertEquals(CheckInStatus.EVENT_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest("not-existing-event", "not-existing-ticket"), sponsorPrincipal).getBody().getResult().getStatus()); - assertEquals(CheckInStatus.TICKET_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, "not-existing-ticket"), sponsorPrincipal).getBody().getResult().getStatus()); - assertEquals(CheckInStatus.INVALID_TICKET_STATE, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketIdentifier), sponsorPrincipal).getBody().getResult().getStatus()); - // - - - - // check stats after revert check in one ticket - assertFalse(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 1)).isEmpty()); - EventWithAdditionalInfo eventWithAdditionalInfo4 = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user); - assertEquals(0, eventWithAdditionalInfo4.getNotSoldTickets()); - assertEquals(1, eventWithAdditionalInfo4.getSoldTickets()); - assertEquals(20, eventWithAdditionalInfo4.getAvailableSeats()); - assertEquals(0, eventWithAdditionalInfo4.getCheckedInTickets()); - - - CheckInApiController.TicketCode tc2 = new CheckInApiController.TicketCode(); - tc2.setCode(ticketCode); - TicketAndCheckInResult ticketAndcheckInResult = checkInApiController.checkIn(event.getId(), ticketIdentifier, tc2, new TestingAuthenticationToken("ciccio", "ciccio")); - assertEquals(CheckInStatus.SUCCESS, ticketAndcheckInResult.getResult().getStatus()); - // - - - // - var offlineIdentifiers = checkInApiController.getOfflineIdentifiers(event.getShortName(), 0L, new MockHttpServletResponse(), principal); - assertFalse("Alf.io-PI integration must be enabled by default", offlineIdentifiers.isEmpty()); - - //disable Alf.io-PI - configurationRepository.insert(ConfigurationKeys.ALFIO_PI_INTEGRATION_ENABLED.name(), "false", null); - offlineIdentifiers = checkInApiController.getOfflineIdentifiers(event.getShortName(), 0L, new MockHttpServletResponse(), principal); - assertTrue(offlineIdentifiers.isEmpty()); - - //re-enable Alf.io-PI - configurationRepository.insertEventLevel(event.getOrganizationId(), event.getId(), ConfigurationKeys.OFFLINE_CHECKIN_ENABLED.name(), "true", null); - configurationRepository.update(ConfigurationKeys.ALFIO_PI_INTEGRATION_ENABLED.name(), "true"); - offlineIdentifiers = checkInApiController.getOfflineIdentifiers(event.getShortName(), 0L, new MockHttpServletResponse(), principal); - assertFalse(offlineIdentifiers.isEmpty()); - Map payload = checkInApiController.getOfflineEncryptedInfo(event.getShortName(), Collections.emptyList(), offlineIdentifiers, principal); - assertEquals(1, payload.size()); - TicketWithCategory ticket = ticketAndcheckInResult.getTicket(); - String ticketKey = ticket.hmacTicketInfo(event.getPrivateKey()); - String hashedTicketKey = DigestUtils.sha256Hex(ticketKey); - String encJson = payload.get(hashedTicketKey); - assertNotNull(encJson); - String ticketPayload = CheckInManager.decrypt(ticket.getUuid() + "/" + ticketKey, encJson); - Map jsonPayload = Json.fromJson(ticketPayload, new TypeReference>() { - }); - assertNotNull(jsonPayload); - assertEquals(8, jsonPayload.size()); - assertEquals("Test", jsonPayload.get("firstName")); - assertEquals("OTest", jsonPayload.get("lastName")); - assertEquals("Test OTest", jsonPayload.get("fullName")); - assertEquals(ticket.getUuid(), jsonPayload.get("uuid")); - assertEquals("testmctest@test.com", jsonPayload.get("email")); - assertEquals("CHECKED_IN", jsonPayload.get("status")); - String categoryName = ticketCategoryRepository.findByEventId(event.getId()).stream().findFirst().orElseThrow(IllegalStateException::new).getName(); - assertEquals(categoryName, jsonPayload.get("category")); - // - - // check register sponsor scan success flow - assertTrue(attendeeApiController.getScannedBadges(event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().isEmpty()); - assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticket.getUuid()), sponsorPrincipal).getBody().getResult().getStatus()); - assertEquals(1, attendeeApiController.getScannedBadges(event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().size()); - - // check export - MockHttpServletResponse response = new MockHttpServletResponse(); - eventApiController.downloadSponsorScanExport(event.getShortName(), "csv", response, principal); - response.getContentAsString(); - CSVReader csvReader = new CSVReader(new StringReader(response.getContentAsString())); - List csvSponsorScan = csvReader.readAll(); - Assert.assertEquals(2, csvSponsorScan.size()); - Assert.assertEquals("sponsor", csvSponsorScan.get(1)[0]); - Assert.assertEquals("Test OTest", csvSponsorScan.get(1)[3]); - Assert.assertEquals("testmctest@test.com", csvSponsorScan.get(1)[4]); - // - - eventManager.deleteEvent(event.getId(), principal.getName()); - - } - - private void checkCalendar(String eventName) throws IOException { - MockHttpServletResponse resIcal = new MockHttpServletResponse(); - eventController.calendar(eventName, "en", null, null, resIcal); - assertEquals("text/calendar", resIcal.getContentType()); - - MockHttpServletResponse resGoogleCal = new MockHttpServletResponse(); - eventController.calendar(eventName, "en", "google", null, resGoogleCal); - Assert.assertTrue(resGoogleCal.getRedirectedUrl().startsWith("https://www.google.com/calendar/event")); - } - - private TicketDecorator checkReservationComplete(String eventName, String reservationIdentifier) { - Model confirmationPageModel = new BindingAwareModelMap(); - String confirmationPageSuccess = reservationController.showConfirmationPage(eventName, reservationIdentifier, false, false, confirmationPageModel, Locale.ENGLISH, new MockHttpServletRequest()); - assertEquals("/event/reservation-page-complete", confirmationPageSuccess); - @SuppressWarnings("unchecked") - List>> tickets = (List>>) confirmationPageModel.asMap().get("ticketsByCategory"); - assertEquals(1, tickets.size()); - assertEquals(1, tickets.get(0).getRight().size()); - return tickets.get(0).getRight().get(0); - } - - private void assignTicket(String eventName, String reservationIdentifier, String ticketIdentifier, String firstName, String lastName) throws Exception { - UpdateTicketOwnerForm ticketOwnerForm = new UpdateTicketOwnerForm(); - ticketOwnerForm.setFirstName(firstName); - ticketOwnerForm.setLastName(lastName); - ticketOwnerForm.setEmail("testmctest@test.com"); - ticketOwnerForm.setUserLanguage("en"); - Assert.assertTrue(reservationController.assignTicketToPerson(eventName, reservationIdentifier, ticketIdentifier, ticketOwnerForm, mock(BindingResult.class), new MockHttpServletRequest(), new BindingAwareModelMap()).endsWith("/success")); - } - - private void checkCSV(String eventName, String ticketIdentifier, String fullName) throws IOException { - //FIXME get all fields :D and put it in the request... - Principal principal = mock(Principal.class); - Mockito.when(principal.getName()).thenReturn(user); - MockHttpServletResponse response = new MockHttpServletResponse(); - List> fields = eventApiController.getAllFields(eventName, principal); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setParameter("fields", fields.stream().map(SerializablePair::getKey).toArray(String[]::new)); - eventApiController.downloadAllTicketsCSV(eventName, "csv", request, response, principal); - CSVReader csvReader = new CSVReader(new StringReader(response.getContentAsString())); - List csv = csvReader.readAll(); - assertEquals(2, csv.size()); - assertEquals(ticketIdentifier, csv.get(1)[0]); - assertEquals("default", csv.get(1)[2]); - assertEquals("ACQUIRED", csv.get(1)[4]); - assertEquals(fullName, csv.get(1)[10]); - } - - private void validatePayment(String eventName, String reservationIdentifier) { - Principal principal = mock(Principal.class); - Mockito.when(principal.getName()).thenReturn(user); - var reservation = ticketReservationRepository.findReservationById(reservationIdentifier); - assertEquals(900, reservation.getFinalPriceCts()); - assertEquals(1000, reservation.getSrcPriceCts()); - assertEquals(9, reservation.getVatCts()); - assertEquals(100, reservation.getDiscountCts()); - assertEquals(1, eventApiController.getPendingPayments(eventName).size()); - assertEquals("OK", eventApiController.confirmPayment(eventName, reservationIdentifier, principal, new BindingAwareModelMap(), new MockHttpServletRequest())); - assertEquals(0, eventApiController.getPendingPayments(eventName).size()); - assertEquals(900, eventRepository.getGrossIncome(event.getId())); - } - - private String payOffline(String eventName, String reservationIdentifier) { - ContactAndTicketsForm contactAndTicketsForm = new ContactAndTicketsForm(); - - contactAndTicketsForm.setEmail("test@test.com"); - contactAndTicketsForm.setBillingAddress("my billing address"); - contactAndTicketsForm.setFirstName("full"); - contactAndTicketsForm.setLastName("name"); - contactAndTicketsForm.setPostponeAssignment(true); - BindingResult bindingResult = new BeanPropertyBindingResult(contactAndTicketsForm, "paymentForm"); - Model model = new BindingAwareModelMap(); - MockHttpServletRequest request = new MockHttpServletRequest(); - RedirectAttributes redirectAttributes = new RedirectAttributesModelMap(); - - reservationController.validateToOverview(eventName, reservationIdentifier, contactAndTicketsForm, bindingResult, request, redirectAttributes, Locale.ENGLISH); - - Assert.assertEquals("/event/overview", reservationController.showOverview(eventName, reservationIdentifier, Locale.ENGLISH, model, new MockHttpSession())); - - PaymentForm paymentForm = new PaymentForm(); - paymentForm.setPaymentMethod(PaymentProxy.OFFLINE); - paymentForm.setTermAndConditionsAccepted(true); - paymentForm.setPrivacyPolicyAccepted(true); - return reservationController.handleReservation(eventName, reservationIdentifier, paymentForm, bindingResult, model, request, Locale.ENGLISH, redirectAttributes, new MockHttpSession()); - } - - private String reserveTicket(String eventName) { - - MockHttpServletRequest requestPromo = new MockHttpServletRequest(); - //apply promo code - ValidationResult res = eventController.savePromoCode(event.getShortName(), PROMO_CODE, new BindingAwareModelMap(), requestPromo); - Assert.assertTrue(res.isSuccess()); - // - ReservationForm reservationForm = new ReservationForm(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setSession(requestPromo.getSession()); - request.setMethod("POST"); - ServletWebRequest servletWebRequest = new ServletWebRequest(request); - BindingResult bindingResult = new BeanPropertyBindingResult(reservationForm, "reservation"); - - RedirectAttributes redirectAttributes = new RedirectAttributesModelMap(); - TicketReservationModification ticketReservation = new TicketReservationModification(); - ticketReservation.setAmount(1); - ticketReservation.setTicketCategoryId(ticketCategoryRepository.findByEventId(event.getId()).stream().findFirst().map(TicketCategory::getId).orElseThrow(IllegalStateException::new)); - reservationForm.setReservation(Collections.singletonList(ticketReservation)); - - - return eventController.reserveTicket(eventName, reservationForm, bindingResult, servletWebRequest, redirectAttributes, Locale.ENGLISH); - } - -} diff --git a/src/test/java/alfio/controller/api/v1/EventApiV1IntegrationTest.java b/src/test/java/alfio/controller/api/v1/EventApiV1IntegrationTest.java index 89c3c3b131..5c51ca9877 100644 --- a/src/test/java/alfio/controller/api/v1/EventApiV1IntegrationTest.java +++ b/src/test/java/alfio/controller/api/v1/EventApiV1IntegrationTest.java @@ -19,8 +19,8 @@ import alfio.TestConfiguration; import alfio.config.DataSourceConfiguration; import alfio.config.Initializer; -import alfio.controller.ReservationFlowIntegrationTest; import alfio.controller.api.v1.admin.EventApiV1Controller; +import alfio.controller.api.v2.user.ReservationFlowIntegrationTest; import alfio.manager.EventManager; import alfio.manager.user.UserManager; import alfio.model.Event; diff --git a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java index 171ead440a..7274a034e4 100644 --- a/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/ReservationFlowIntegrationTest.java @@ -19,35 +19,47 @@ import alfio.TestConfiguration; import alfio.config.DataSourceConfiguration; import alfio.config.Initializer; +import alfio.controller.api.AttendeeApiController; import alfio.controller.api.admin.AdditionalServiceApiController; +import alfio.controller.api.admin.CheckInApiController; import alfio.controller.api.admin.EventApiController; +import alfio.controller.api.admin.UsersApiController; import alfio.controller.api.v2.InfoApiController; import alfio.controller.api.v2.TranslationsApiController; import alfio.controller.api.v2.model.EventCode; import alfio.controller.api.v2.model.Language; import alfio.controller.form.*; +import alfio.manager.CheckInManager; import alfio.manager.EventManager; import alfio.manager.EventStatisticsManager; +import alfio.manager.support.CheckInStatus; +import alfio.manager.support.TicketAndCheckInResult; import alfio.manager.user.UserManager; import alfio.model.*; -import alfio.model.modification.DateTimeModification; -import alfio.model.modification.EventModification; -import alfio.model.modification.TicketCategoryModification; -import alfio.model.modification.TicketReservationModification; +import alfio.model.audit.ScanAudit; +import alfio.model.modification.*; import alfio.model.system.ConfigurationKeys; import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.PaymentProxy; +import alfio.model.user.User; import alfio.repository.*; +import alfio.repository.audit.ScanAuditRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; +import alfio.util.EventUtil; +import alfio.util.Json; +import com.fasterxml.jackson.core.type.TypeReference; import com.google.zxing.BinaryBitmap; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.qrcode.QRCodeReader; +import com.opencsv.CSVReader; +import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.Pair; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -58,6 +70,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -65,14 +78,15 @@ import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.support.BindingAwareModelMap; import org.springframework.web.context.request.ServletWebRequest; -import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import javax.imageio.ImageIO; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.StringReader; import java.math.BigDecimal; import java.security.Principal; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.*; @@ -84,7 +98,7 @@ import static org.mockito.Mockito.mock; @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, alfio.controller.ReservationFlowIntegrationTest.ControllerConfiguration.class}) +@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ReservationFlowIntegrationTest.ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) @Transactional public class ReservationFlowIntegrationTest extends BaseIntegrationTest { @@ -131,6 +145,20 @@ public static class ControllerConfiguration { @Autowired private AdditionalServiceApiController additionalServiceApiController; + // + @Autowired + private CheckInApiController checkInApiController; + + @Autowired + private AttendeeApiController attendeeApiController; + + @Autowired + private UsersApiController usersApiController; + + @Autowired + private ScanAuditRepository scanAuditRepository; + // + // @Autowired private InfoApiController infoApiController; @@ -627,6 +655,128 @@ public void reservationFlowTest() throws Exception { assertEquals(HttpStatus.NOT_FOUND, reservationApiV2Controller.getInvoice(event.getShortName(), reservationId, new MockHttpServletResponse(), null).getStatusCode()); assertEquals(HttpStatus.OK, reservationApiV2Controller.getReceipt(event.getShortName(), reservationId, new MockHttpServletResponse(), null).getStatusCode()); + + + // + + { + + Principal principal = mock(Principal.class); + Mockito.when(principal.getName()).thenReturn(user); + + String ticketIdentifier = fullTicketInfo.getUuid(); + String eventName = event.getShortName(); + + String ticketCode = fullTicketInfo.ticketCode(event.getPrivateKey()); + TicketAndCheckInResult ticketAndCheckInResult = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode); + assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult.getResult().getStatus()); + CheckInApiController.TicketCode tc = new CheckInApiController.TicketCode(); + tc.setCode(ticketCode); + assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(event.getId(), ticketIdentifier, tc, new TestingAuthenticationToken("ciccio", "ciccio")).getResult().getStatus()); + List audits = scanAuditRepository.findAllForEvent(event.getId()); + assertFalse(audits.isEmpty()); + assertTrue(audits.stream().anyMatch(sa -> sa.getTicketUuid().equals(ticketIdentifier))); + + + TicketAndCheckInResult ticketAndCheckInResultOk = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode); + assertEquals(CheckInStatus.ALREADY_CHECK_IN, ticketAndCheckInResultOk.getResult().getStatus()); + + // check stats after check in one ticket + assertFalse(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 1)).isEmpty()); + EventWithAdditionalInfo eventWithAdditionalInfo3 = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user); + assertEquals(2, eventWithAdditionalInfo3.getNotSoldTickets()); + assertEquals(0, eventWithAdditionalInfo3.getSoldTickets()); + assertEquals(20, eventWithAdditionalInfo3.getAvailableSeats()); + assertEquals(1, eventWithAdditionalInfo3.getCheckedInTickets()); + + + //test revert check in + assertTrue(checkInApiController.revertCheckIn(event.getId(), ticketIdentifier, principal)); + assertFalse(checkInApiController.revertCheckIn(event.getId(), ticketIdentifier, principal)); + TicketAndCheckInResult ticketAndCheckInResult2 = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode); + assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult2.getResult().getStatus()); + + UsersApiController.UserWithPasswordAndQRCode sponsorUser = usersApiController.insertUser(new UserModification(null, event.getOrganizationId(), "SPONSOR", "sponsor", "first", "last", "email@email.com", User.Type.INTERNAL, null, null), "http://localhost:8080", principal); + Principal sponsorPrincipal = mock(Principal.class); + Mockito.when(sponsorPrincipal.getName()).thenReturn(sponsorUser.getUsername()); + + // check failures + assertEquals(CheckInStatus.EVENT_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest("not-existing-event", "not-existing-ticket"), sponsorPrincipal).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.TICKET_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, "not-existing-ticket"), sponsorPrincipal).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.INVALID_TICKET_STATE, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketIdentifier), sponsorPrincipal).getBody().getResult().getStatus()); + // + + + // check stats after revert check in one ticket + assertFalse(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 1)).isEmpty()); + EventWithAdditionalInfo eventWithAdditionalInfo4 = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user); + assertEquals(2, eventWithAdditionalInfo4.getNotSoldTickets()); + assertEquals(1, eventWithAdditionalInfo4.getSoldTickets()); + assertEquals(20, eventWithAdditionalInfo4.getAvailableSeats()); + assertEquals(0, eventWithAdditionalInfo4.getCheckedInTickets()); + + + CheckInApiController.TicketCode tc2 = new CheckInApiController.TicketCode(); + tc2.setCode(ticketCode); + TicketAndCheckInResult ticketAndcheckInResult = checkInApiController.checkIn(event.getId(), ticketIdentifier, tc2, new TestingAuthenticationToken("ciccio", "ciccio")); + assertEquals(CheckInStatus.SUCCESS, ticketAndcheckInResult.getResult().getStatus()); + // + + + // + var offlineIdentifiers = checkInApiController.getOfflineIdentifiers(event.getShortName(), 0L, new MockHttpServletResponse(), principal); + assertFalse("Alf.io-PI integration must be enabled by default", offlineIdentifiers.isEmpty()); + + //disable Alf.io-PI + configurationRepository.insert(ConfigurationKeys.ALFIO_PI_INTEGRATION_ENABLED.name(), "false", null); + offlineIdentifiers = checkInApiController.getOfflineIdentifiers(event.getShortName(), 0L, new MockHttpServletResponse(), principal); + assertTrue(offlineIdentifiers.isEmpty()); + + //re-enable Alf.io-PI + configurationRepository.insertEventLevel(event.getOrganizationId(), event.getId(), ConfigurationKeys.OFFLINE_CHECKIN_ENABLED.name(), "true", null); + configurationRepository.update(ConfigurationKeys.ALFIO_PI_INTEGRATION_ENABLED.name(), "true"); + offlineIdentifiers = checkInApiController.getOfflineIdentifiers(event.getShortName(), 0L, new MockHttpServletResponse(), principal); + assertFalse(offlineIdentifiers.isEmpty()); + Map payload = checkInApiController.getOfflineEncryptedInfo(event.getShortName(), Collections.emptyList(), offlineIdentifiers, principal); + assertEquals(1, payload.size()); + TicketWithCategory ticketwc = ticketAndcheckInResult.getTicket(); + String ticketKey = ticketwc.hmacTicketInfo(event.getPrivateKey()); + String hashedTicketKey = DigestUtils.sha256Hex(ticketKey); + String encJson = payload.get(hashedTicketKey); + assertNotNull(encJson); + String ticketPayload = CheckInManager.decrypt(ticketwc.getUuid() + "/" + ticketKey, encJson); + Map jsonPayload = Json.fromJson(ticketPayload, new TypeReference>() { + }); + assertNotNull(jsonPayload); + assertEquals(8, jsonPayload.size()); + assertEquals("Test", jsonPayload.get("firstName")); + assertEquals("Testson", jsonPayload.get("lastName")); + assertEquals("Test Testson", jsonPayload.get("fullName")); + assertEquals(ticketwc.getUuid(), jsonPayload.get("uuid")); + assertEquals("testmctest@test.com", jsonPayload.get("email")); + assertEquals("CHECKED_IN", jsonPayload.get("status")); + assertEquals("default", jsonPayload.get("category")); + // + + // check register sponsor scan success flow + assertTrue(attendeeApiController.getScannedBadges(event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().isEmpty()); + assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketwc.getUuid()), sponsorPrincipal).getBody().getResult().getStatus()); + assertEquals(1, attendeeApiController.getScannedBadges(event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().size()); + + // check export + MockHttpServletResponse response = new MockHttpServletResponse(); + eventApiController.downloadSponsorScanExport(event.getShortName(), "csv", response, principal); + response.getContentAsString(); + CSVReader csvReader = new CSVReader(new StringReader(response.getContentAsString())); + List csvSponsorScan = csvReader.readAll(); + Assert.assertEquals(2, csvSponsorScan.size()); + Assert.assertEquals("sponsor", csvSponsorScan.get(1)[0]); + Assert.assertEquals("Test Testson", csvSponsorScan.get(1)[3]); + Assert.assertEquals("testmctest@test.com", csvSponsorScan.get(1)[4]); + // + + eventManager.deleteEvent(event.getId(), principal.getName()); + } } } diff --git a/src/test/java/alfio/controller/decorator/DecoratorUtilTest.java b/src/test/java/alfio/controller/decorator/DecoratorUtilTest.java deleted file mode 100644 index 9a42edeab8..0000000000 --- a/src/test/java/alfio/controller/decorator/DecoratorUtilTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see . - */ -package alfio.controller.decorator; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -public class DecoratorUtilTest { - - @Test - public void range0to5() { - assertArrayEquals(new int[]{0,1,2,3,4,5}, DecoratorUtil.generateRangeOfTicketQuantity(5,5)); - } - - @Test - public void range0to1() { - assertArrayEquals(new int[]{0,1}, DecoratorUtil.generateRangeOfTicketQuantity(1, 50)); - } - - @Test - public void range0to0() { - assertArrayEquals(new int[]{0}, DecoratorUtil.generateRangeOfTicketQuantity(-1, 50)); - } - -} \ No newline at end of file