alfio.util.TemplateResource.java Source code

Java tutorial

Introduction

Here is the source code for alfio.util.TemplateResource.java

Source

/**
 * 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 <http://www.gnu.org/licenses/>.
 */
package alfio.util;

import alfio.model.*;
import alfio.model.modification.SendCodeModification;
import alfio.model.transaction.PaymentProxy;
import alfio.model.user.Organization;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static alfio.util.ImageUtil.createQRCode;

public enum TemplateResource {
    GOOGLE_ANALYTICS("/alfio/templates/google-analytics.ms", false, "text/plain",
            TemplateManager.TemplateOutput.TEXT),

    CONFIRMATION_EMAIL_FOR_ORGANIZER("/alfio/templates/confirmation-email-for-organizer-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return prepareSampleDataForConfirmationEmail(organization, event);
        }
    },
    SEND_RESERVED_CODE("/alfio/templates/send-reserved-code-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return prepareModelForSendReservedCode(organization, event,
                    new SendCodeModification("CODE", "Firstname Lastname", "email@email.tld", "en"),
                    "http://your-domain.tld/event-page");
        }
    },
    CONFIRMATION_EMAIL("/alfio/templates/confirmation-email-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return prepareSampleDataForConfirmationEmail(organization, event);
        }
    },
    OFFLINE_RESERVATION_EXPIRED_EMAIL("/alfio/templates/offline-reservation-expired-email-txt.ms", true,
            "text/plain", TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return prepareSampleDataForConfirmationEmail(organization, event);
        }
    },
    OFFLINE_RESERVATION_EXPIRING_EMAIL_FOR_ORGANIZER(
            "/alfio/templates/offline-reservation-expiring-email-for-organizer-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return prepareSampleModelForOfflineReservationExpiringEmailForOrganizer(event);
        }
    },
    REMINDER_EMAIL("/alfio/templates/reminder-email-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return prepareSampleDataForConfirmationEmail(organization, event);
        }
    },
    REMINDER_TICKET_ADDITIONAL_INFO("/alfio/templates/reminder-ticket-additional-info.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return prepareModelForReminderTicketAdditionalInfo(organization, event, sampleTicket(),
                    "http://your-domain.tld/ticket-url");
        }
    },
    REMINDER_TICKETS_ASSIGNMENT_EMAIL("/alfio/templates/reminder-tickets-assignment-email-txt.ms", true,
            "text/plain", TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return prepareSampleDataForConfirmationEmail(organization, event);
        }
    },

    TICKET_EMAIL("/alfio/templates/ticket-email-txt.ms", true, "text/plain", TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            TicketCategory ticketCategory = new TicketCategory(0, ZonedDateTime.now(), ZonedDateTime.now(), 42,
                    "Ticket", false, TicketCategory.Status.ACTIVE, event.getId(), false, 1000, null, null, null,
                    null, null);
            return buildModelForTicketEmail(organization, event, sampleTicketReservation(),
                    "http://your-domain.tld/ticket-url", sampleTicket(), ticketCategory);
        }
    },
    TICKET_HAS_CHANGED_OWNER("/alfio/templates/ticket-has-changed-owner-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return buildModelForTicketHasChangedOwner(organization, event, sampleTicket(),
                    sampleTicket("NewFirstname", "NewLastname", "newemail@email.tld"),
                    "http://your-domain.tld/ticket-url");
        }
    },

    TICKET_HAS_BEEN_CANCELLED("/alfio/templates/ticket-has-been-cancelled-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return buildModelForTicketHasBeenCancelled(organization, event, sampleTicket());
        }
    },

    TICKET_HAS_BEEN_CANCELLED_ADMIN("/alfio/templates/ticket-has-been-cancelled-admin-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return buildModelForTicketHasBeenCancelledAdmin(organization, event, sampleTicket(), "Category",
                    Collections.emptyList(), asi -> Optional.empty());
        }
    },

    TICKET_PDF("/alfio/templates/ticket.ms", true, "application/pdf", TemplateManager.TemplateOutput.HTML) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            TicketCategory ticketCategory = new TicketCategory(0, ZonedDateTime.now(), ZonedDateTime.now(), 42,
                    "Ticket", false, TicketCategory.Status.ACTIVE, event.getId(), false, 1000, null, null, null,
                    null, null);
            return buildModelForTicketPDF(organization, event, sampleTicketReservation(), ticketCategory,
                    sampleTicket(), imageData, "ABCD");
        }
    },
    RECEIPT_PDF("/alfio/templates/receipt.ms", true, "application/pdf", TemplateManager.TemplateOutput.HTML) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            Map<String, Object> model = prepareSampleDataForConfirmationEmail(organization, event);
            imageData.ifPresent(iData -> {
                model.put("eventImage", iData.getEventImage());
                model.put("imageWidth", iData.getImageWidth());
                model.put("imageHeight", iData.getImageHeight());
            });
            return model;
        }
    },

    INVOICE_PDF("/alfio/templates/invoice.ms", true, "application/pdf", TemplateManager.TemplateOutput.HTML) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            Map<String, Object> model = prepareSampleDataForConfirmationEmail(organization, event);
            imageData.ifPresent(iData -> {
                model.put("eventImage", iData.getEventImage());
                model.put("imageWidth", iData.getImageWidth());
                model.put("imageHeight", iData.getImageHeight());
            });
            return model;
        }
    },

    WAITING_QUEUE_JOINED("/alfio/templates/waiting-queue-joined.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            return buildModelForWaitingQueueJoined(organization, event,
                    new CustomerName("Firstname Lastname", "Firstname", "Lastname", event));
        }
    },
    WAITING_QUEUE_RESERVATION_EMAIL("/alfio/templates/waiting-queue-reservation-email-txt.ms", true, "text/plain",
            TemplateManager.TemplateOutput.TEXT) {
        @Override
        public Map<String, Object> prepareSampleModel(Organization organization, Event event,
                Optional<ImageData> imageData) {
            WaitingQueueSubscription subscription = new WaitingQueueSubscription(0, ZonedDateTime.now(),
                    event.getId(), "ACQUIRED", "Firstname Lastname", "Firstname", "Lastname", "email@email.tld",
                    "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", "en", null, WaitingQueueSubscription.Type.PRE_SALES);
            return buildModelForWaitingQueueReservationEmail(organization, event, subscription,
                    "http://your-domain.tld/reservation-url", ZonedDateTime.now());
        }
    };

    private final String classPathUrl;
    private final boolean overridable;
    private final String renderedContentType;
    private final TemplateManager.TemplateOutput templateOutput;

    TemplateResource(String classPathUrl, boolean overridable, String renderedContentType,
            TemplateManager.TemplateOutput templateOutput) {
        this.classPathUrl = classPathUrl;
        this.overridable = overridable;
        this.renderedContentType = renderedContentType;
        this.templateOutput = templateOutput;
    }

    public String getSavedName(Locale locale) {
        return name() + "_" + locale.getLanguage() + ".ms";
    }

    public boolean overridable() {
        return overridable;
    }

    public String classPath() {
        return classPathUrl;
    }

    public String getRenderedContentType() {
        return renderedContentType;
    }

    public TemplateManager.TemplateOutput getTemplateOutput() {
        return templateOutput;
    }

    public Map<String, Object> prepareSampleModel(Organization organization, Event event,
            Optional<ImageData> imageData) {
        return Collections.emptyMap();
    }

    private static Ticket sampleTicket() {
        return sampleTicket("Firstname", "Lastname", "email@email.tld");
    }

    private static Ticket sampleTicket(String firstName, String lastName, String email) {
        return new Ticket(0, "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", ZonedDateTime.now(), 0, "ACQUIRED", 0,
                "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", firstName + " " + lastName, firstName, lastName, email,
                false, "en", 1000, 1000, 80, 0, null);
    }

    private static TicketReservation sampleTicketReservation() {
        return new TicketReservation("597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", new Date(),
                TicketReservation.TicketReservationStatus.COMPLETE, "Firstname Lastname", "FirstName", "Lastname",
                "email@email.tld", "billing address", ZonedDateTime.now(), ZonedDateTime.now(), PaymentProxy.STRIPE,
                true, null, false, "en", false, null, null, null, "123456", "CH", false, new BigDecimal("8.00"),
                true);
    }

    private static Map<String, Object> prepareSampleDataForConfirmationEmail(Organization organization,
            Event event) {
        TicketReservation reservation = sampleTicketReservation();
        Optional<String> vat = Optional.of("VAT-NR");
        List<Ticket> tickets = Collections.singletonList(sampleTicket());
        OrderSummary orderSummary = new OrderSummary(new TotalPrice(1000, 80, 0, 0),
                Collections.singletonList(new SummaryRow("Ticket", "10.00", "9.20", 1, "9.20", "9.20", 1000,
                        SummaryRow.SummaryType.TICKET)),
                false, "10.00", "0.80", false, false, "8", PriceContainer.VatStatus.INCLUDED);
        String reservationUrl = "http://your-domain.tld/reservation-url/";
        String reservationShortId = "597e7e7b";
        return prepareModelForConfirmationEmail(organization, event, reservation, vat, tickets, orderSummary,
                reservationUrl, reservationShortId, Optional.of("My Invoice\nAddress"), Optional.empty(),
                Optional.empty());
    }

    //used by multiple enum:
    // - CONFIRMATION_EMAIL_FOR_ORGANIZER
    // - OFFLINE_RESERVATION_EXPIRED_EMAIL
    // - REMINDER_TICKETS_ASSIGNMENT_EMAIL
    // - CONFIRMATION_EMAIL
    // - REMINDER_EMAIL
    // - RECEIPT_PDF + ImageData
    // - INVOICE_PDF + ImageData
    public static Map<String, Object> prepareModelForConfirmationEmail(Organization organization, Event event,
            TicketReservation reservation, Optional<String> vat, List<Ticket> tickets, OrderSummary orderSummary,
            String reservationUrl, String reservationShortID, Optional<String> invoiceAddress,
            Optional<String> bankAccountNr, Optional<String> bankAccountOwner) {
        Map<String, Object> model = new HashMap<>();
        model.put("organization", organization);
        model.put("event", event);
        model.put("ticketReservation", reservation);
        model.put("hasVat", vat.isPresent());
        model.put("vatNr", vat.orElse(""));
        model.put("tickets", tickets);
        model.put("orderSummary", orderSummary);
        model.put("reservationUrl", reservationUrl);
        model.put("locale", reservation.getUserLanguage());

        ZonedDateTime confirmationTimestamp = Optional.ofNullable(reservation.getConfirmationTimestamp())
                .orElseGet(ZonedDateTime::now);
        model.put("confirmationDate", confirmationTimestamp.withZoneSameInstant(event.getZoneId()));

        if (reservation.getValidity() != null) {
            model.put("expirationDate",
                    ZonedDateTime.ofInstant(reservation.getValidity().toInstant(), event.getZoneId()));
        }

        model.put("reservationShortID", reservationShortID);

        model.put("hasInvoiceAddress", invoiceAddress.isPresent());
        invoiceAddress.ifPresent(addr -> {
            model.put("invoiceAddress", StringUtils.replace(addr, "\n", ", "));
            model.put("invoiceAddressAsList", Arrays.asList(StringUtils.split(addr, '\n')));
        });

        model.put("hasBankAccountNr", bankAccountNr.isPresent());
        bankAccountNr.ifPresent(nr -> {
            model.put("bankAccountNr", nr);
        });

        model.put("isOfflinePayment",
                reservation.getStatus() == TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT);
        model.put("paymentReason", event.getShortName() + " " + reservationShortID);
        model.put("hasBankAccountOnwer", bankAccountOwner.isPresent());
        bankAccountOwner.ifPresent(owner -> {
            model.put("bankAccountOnwer", StringUtils.replace(owner, "\n", ", "));
            model.put("bankAccountOnwerAsList", Arrays.asList(StringUtils.split(owner, '\n')));
        });

        return model;
    }

    // used by OFFLINE_RESERVATION_EXPIRING_EMAIL_FOR_ORGANIZER
    public static Map<String, Object> prepareModelForOfflineReservationExpiringEmailForOrganizer(Event event,
            List<TicketReservationInfo> reservations, String baseUrl) {
        Map<String, Object> model = new HashMap<>();
        model.put("eventName", event.getDisplayName());
        model.put("ticketReservations", reservations);
        model.put("baseUrl", baseUrl);
        model.put("eventShortName", event.getShortName());
        return model;
    }

    public static Map<String, Object> prepareSampleModelForOfflineReservationExpiringEmailForOrganizer(
            Event event) {
        Map<String, Object> model = new HashMap<>();
        model.put("eventName", event.getDisplayName());
        model.put("ticketReservations", Collections.singletonList(
                new TicketReservationInfo("id", null, "Firstname", "Lastname", "email@email.email", 42)));
        model.put("baseUrl", "http://base-url/");
        model.put("eventShortName", event.getShortName());
        return model;
    }

    // used by SEND_RESERVED_CODE
    public static Map<String, Object> prepareModelForSendReservedCode(Organization organization, Event event,
            SendCodeModification m, String eventPageUrl) {
        Map<String, Object> model = new HashMap<>();
        model.put("code", m.getCode());
        model.put("event", event);
        model.put("organization", organization);
        model.put("eventPage", eventPageUrl);
        model.put("assignee", m.getAssignee());
        return model;
    }

    // used by REMINDER_TICKET_ADDITIONAL_INFO
    public static Map<String, Object> prepareModelForReminderTicketAdditionalInfo(Organization organization,
            Event event, Ticket ticket, String ticketUrl) {
        Map<String, Object> model = new HashMap<>();
        model.put("event", event);
        model.put("fullName", ticket.getFullName());
        model.put("organization", organization);
        model.put("ticketURL", ticketUrl);
        return model;
    }

    // used by TICKET_EMAIL
    public static Map<String, Object> buildModelForTicketEmail(Organization organization, Event event,
            TicketReservation ticketReservation, String ticketURL, Ticket ticket, TicketCategory ticketCategory) {
        Map<String, Object> model = new HashMap<>();
        model.put("organization", organization);
        model.put("event", event);
        model.put("ticketReservation", ticketReservation);
        model.put("ticketUrl", ticketURL);
        model.put("ticket", ticket);
        model.put("googleCalendarUrl", EventUtil.getGoogleCalendarURL(event, ticketCategory, null));
        fillTicketValidity(event, ticketCategory, model);
        return model;
    }

    public static void fillTicketValidity(Event event, TicketCategory ticketCategory, Map<String, Object> model) {
        model.put("validityStart", Optional.ofNullable(ticketCategory.getTicketValidityStart(event.getZoneId()))
                .orElse(event.getBegin()));
        model.put("validityEnd",
                Optional.ofNullable(ticketCategory.getTicketValidityEnd(event.getZoneId())).orElse(event.getEnd()));
    }

    // used by TICKET_HAS_CHANGED_OWNER
    public static Map<String, Object> buildModelForTicketHasChangedOwner(Organization organization, Event e,
            Ticket oldTicket, Ticket newTicket, String ticketUrl) {
        Map<String, Object> emailModel = new HashMap<>();
        emailModel.put("ticket", oldTicket);
        emailModel.put("organization", organization);
        emailModel.put("eventName", e.getDisplayName());
        emailModel.put("previousEmail", oldTicket.getEmail());
        emailModel.put("newEmail", newTicket.getEmail());
        emailModel.put("ticketUrl", ticketUrl);
        return emailModel;
    }

    // used by TICKET_HAS_BEEN_CANCELLED
    public static Map<String, Object> buildModelForTicketHasBeenCancelled(Organization organization, Event event,
            Ticket ticket) {
        Map<String, Object> model = new HashMap<>();
        model.put("eventName", event.getDisplayName());
        model.put("ticket", ticket);
        model.put("organization", organization);
        return model;
    }

    // used by TICKET_HAS_BEEN_CANCELLED_ADMIN
    public static Map<String, Object> buildModelForTicketHasBeenCancelledAdmin(Organization organization,
            Event event, Ticket ticket, String ticketCategoryDescription,
            List<AdditionalServiceItem> additionalServiceItems,
            Function<AdditionalServiceItem, Optional<AdditionalServiceText>> titleFinder) {
        Map<String, Object> model = buildModelForTicketHasBeenCancelled(organization, event, ticket);
        model.put("ticketCategoryDescription", ticketCategoryDescription);
        model.put("hasAdditionalServices", !additionalServiceItems.isEmpty());
        model.put("additionalServices", additionalServiceItems.stream().map(asi -> {
            Map<String, Object> data = new HashMap<>();
            data.put("name", titleFinder.apply(asi).map(AdditionalServiceText::getValue).orElse("N/A"));
            data.put("amount", MonetaryUtil.centsToUnit(asi.getFinalPriceCts()).toString() + event.getCurrency());
            data.put("id", asi.getId());
            return data;
        }).collect(Collectors.toList()));
        return model;
    }

    // used by TICKET_PDF
    public static Map<String, Object> buildModelForTicketPDF(Organization organization, Event event,
            TicketReservation ticketReservation, TicketCategory ticketCategory, Ticket ticket,
            Optional<ImageData> imageData, String reservationId) {
        String qrCodeText = ticket.ticketCode(event.getPrivateKey());
        //
        Map<String, Object> model = new HashMap<>();
        model.put("ticket", ticket);
        model.put("reservation", ticketReservation);
        model.put("ticketCategory", ticketCategory);
        model.put("event", event);
        model.put("organization", organization);
        model.put("reservationId", reservationId);
        fillTicketValidity(event, ticketCategory, model);

        model.put("qrCodeDataUri",
                "data:image/png;base64," + Base64.getEncoder().encodeToString(createQRCode(qrCodeText)));

        imageData.ifPresent(iData -> {
            model.put("eventImage", iData.getEventImage());
            model.put("imageWidth", iData.getImageWidth());
            model.put("imageHeight", iData.getEventImage());
        });

        model.put("deskPaymentRequired", Optional.ofNullable(ticketReservation.getPaymentMethod())
                .orElse(PaymentProxy.STRIPE).isDeskPaymentRequired());
        return model;
    }

    // used by WAITING_QUEUE_JOINED
    public static Map<String, Object> buildModelForWaitingQueueJoined(Organization organization, Event event,
            CustomerName name) {
        Map<String, Object> model = new HashMap<>();
        model.put("eventName", event.getDisplayName());
        model.put("fullName", name.getFullName());
        model.put("organization", organization);
        return model;
    }

    // used by WAITING_QUEUE_RESERVATION_EMAIL
    public static Map<String, Object> buildModelForWaitingQueueReservationEmail(Organization organization,
            Event event, WaitingQueueSubscription subscription, String reservationUrl, ZonedDateTime expiration) {
        Map<String, Object> model = new HashMap<>();
        model.put("event", event);
        model.put("subscription", subscription);
        model.put("reservationUrl", reservationUrl);
        model.put("reservationTimeout", expiration);
        model.put("organization", organization);
        return model;
    }

    public static ImageData fillWithImageData(FileBlobMetadata m, byte[] image) {

        Map<String, String> attributes = m.getAttributes();
        if (attributes.containsKey(FileBlobMetadata.ATTR_IMG_WIDTH)
                && attributes.containsKey(FileBlobMetadata.ATTR_IMG_HEIGHT)) {
            final int width = Integer.parseInt(attributes.get(FileBlobMetadata.ATTR_IMG_WIDTH));
            final int height = Integer.parseInt(attributes.get(FileBlobMetadata.ATTR_IMG_HEIGHT));
            //in the PDF the image can be maximum 300x150
            int resizedWidth = width;
            int resizedHeight = height;
            if (resizedHeight > 150) {
                resizedHeight = 150;
                resizedWidth = width * resizedHeight / height;
            }
            if (resizedWidth > 300) {
                resizedWidth = 300;
                resizedHeight = height * resizedWidth / width;
            }
            return new ImageData(
                    "data:" + m.getContentType() + ";base64," + Base64.getEncoder().encodeToString(image),
                    resizedWidth, resizedHeight);
        }
        return new ImageData(null, null, null);
    }

    @Getter
    @AllArgsConstructor
    public static class ImageData {
        private final String eventImage;
        private final Integer imageWidth;
        private final Integer imageHeight;
    }
}