diff --git a/web/src/main/java/org/phoenixctms/ctsms/web/jersey/resource/trial/DutyRosterTurnIcsWriter.java b/web/src/main/java/org/phoenixctms/ctsms/web/jersey/resource/trial/DutyRosterTurnIcsWriter.java index db0808e89a44..ff945a12eae1 100644 --- a/web/src/main/java/org/phoenixctms/ctsms/web/jersey/resource/trial/DutyRosterTurnIcsWriter.java +++ b/web/src/main/java/org/phoenixctms/ctsms/web/jersey/resource/trial/DutyRosterTurnIcsWriter.java @@ -4,13 +4,21 @@ import java.util.Collection; import java.util.Date; import java.util.Iterator; +import java.util.Locale; import java.util.TimeZone; +import org.phoenixctms.ctsms.domain.Staff; +import org.phoenixctms.ctsms.domain.User; import org.phoenixctms.ctsms.util.CommonUtil; +import org.phoenixctms.ctsms.util.CoreUtil; +import org.phoenixctms.ctsms.vo.AuthenticationVO; import org.phoenixctms.ctsms.vo.DutyRosterTurnOutVO; import org.phoenixctms.ctsms.vo.StaffOutVO; import org.phoenixctms.ctsms.vo.TrialOutVO; import org.phoenixctms.ctsms.vo.VisitScheduleItemOutVO; +import org.phoenixctms.ctsms.web.util.MessageCodes; +import org.phoenixctms.ctsms.web.util.Messages; +import org.phoenixctms.ctsms.web.util.WebUtil; final class DutyRosterTurnIcsWriter { @@ -18,7 +26,15 @@ final class DutyRosterTurnIcsWriter { private static final String TITLE_SEPARATOR = " - "; private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); - static String toIcalendar(Collection dutyRosterTurns, String host) { + static String calendarName(AuthenticationVO auth) { + StringBuilder sb = new StringBuilder(); + appendPart(sb, org.phoenixctms.ctsms.util.Settings.getInstanceName()); + appendPart(sb, dutyRosterLabel()); + appendPart(sb, identityName(auth)); + return sb.toString(); + } + + static String toIcalendar(Collection dutyRosterTurns, String host, String calendarName) { StringBuilder sb = new StringBuilder(); SimpleDateFormat utc = newUtcFormat(); Date now = new Date(); @@ -27,7 +43,7 @@ static String toIcalendar(Collection dutyRosterTurns, Strin sb.append("PRODID:-//Phoenix CTMS//Duty Roster//EN").append(CRLF); sb.append("CALSCALE:GREGORIAN").append(CRLF); sb.append("METHOD:PUBLISH").append(CRLF); - sb.append("X-WR-CALNAME:Phoenix Duty Roster").append(CRLF); + appendTextLine(sb, "X-WR-CALNAME", calendarName); if (dutyRosterTurns != null) { Iterator it = dutyRosterTurns.iterator(); while (it.hasNext()) { @@ -73,16 +89,15 @@ private static String getSummary(DutyRosterTurnOutVO turn) { if (!CommonUtil.isEmptyString(turn.getTitle())) { appendSeparated(sb, turn.getTitle()); } - StaffOutVO staff = turn.getStaff(); - if (staff != null) { - appendSeparated(sb, CommonUtil.staffOutVOToString(staff)); + if (sb.length() == 0) { + return turn.getCalendar(); } return sb.toString(); } private static String getDescription(DutyRosterTurnOutVO turn) { StringBuilder sb = new StringBuilder(); - if (!CommonUtil.isEmptyString(turn.getCalendar())) { + if (hasEventTitle(turn) && !CommonUtil.isEmptyString(turn.getCalendar())) { sb.append(turn.getCalendar()); } if (!CommonUtil.isEmptyString(turn.getComment())) { @@ -94,6 +109,58 @@ private static String getDescription(DutyRosterTurnOutVO turn) { return sb.toString(); } + private static boolean hasEventTitle(DutyRosterTurnOutVO turn) { + if (turn.getTrial() != null) { + return true; + } + VisitScheduleItemOutVO visitScheduleItem = turn.getVisitScheduleItem(); + if (visitScheduleItem != null && !CommonUtil.isEmptyString(visitScheduleItem.getName())) { + return true; + } + return !CommonUtil.isEmptyString(turn.getTitle()); + } + + private static void appendPart(StringBuilder sb, String value) { + if (CommonUtil.isEmptyString(value)) { + return; + } + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(value.trim()); + } + + private static String dutyRosterLabel() { + Locale locale = null; + try { + locale = CoreUtil.getUserContext().getLocale(); + } catch (Exception e) { + } + try { + return CommonUtil.getString(MessageCodes.DUTY_ROSTER_SCHEDULE_MENU_ITEM_LABEL, CommonUtil.getBundle(Messages.MESSAGE_BUNDLE_DEFAULT, locale), "Duty Roster"); + } catch (Exception e) { + return "Duty Roster"; + } + } + + private static String identityName(AuthenticationVO auth) { + if (auth == null) { + return ""; + } + User user = CoreUtil.getUser(); + Staff identity = user == null ? null : user.getIdentity(); + if (identity == null) { + return ""; + } + try { + StaffOutVO staff = WebUtil.getServiceLocator().getStaffService().getStaff(auth, identity.getId(), null, null, null); + String name = CommonUtil.staffOutVOToString(staff); + return name == null ? "" : name; + } catch (Exception e) { + return ""; + } + } + private static void appendSeparated(StringBuilder sb, String value) { if (sb.length() > 0) { sb.append(TITLE_SEPARATOR); diff --git a/web/src/main/java/org/phoenixctms/ctsms/web/jersey/resource/trial/DutyRosterTurnResource.java b/web/src/main/java/org/phoenixctms/ctsms/web/jersey/resource/trial/DutyRosterTurnResource.java index ee3aa44c436d..e356fd329a84 100644 --- a/web/src/main/java/org/phoenixctms/ctsms/web/jersey/resource/trial/DutyRosterTurnResource.java +++ b/web/src/main/java/org/phoenixctms/ctsms/web/jersey/resource/trial/DutyRosterTurnResource.java @@ -68,8 +68,11 @@ public Response getDutyRosterIcs( @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("sort") Boolean sort) throws Exception { + if (!WebUtil.isDutyRosterIcsRailEnabled()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } Collection dutyRosterTurns = loadDutyRosterInterval(departmentId, statusId, staffId, unassigned, trialId, calendar, from, to, sort, true); - String ics = DutyRosterTurnIcsWriter.toIcalendar(dutyRosterTurns, WebUtil.getHttpHost()); + String ics = DutyRosterTurnIcsWriter.toIcalendar(dutyRosterTurns, WebUtil.getHttpHost(), DutyRosterTurnIcsWriter.calendarName(auth)); ResponseBuilder response = Response.ok(ics, TEXT_CALENDAR + ";charset=UTF-8"); response.header("Content-Disposition", "inline; filename=\"" + ICS_FILENAME + "\""); return response.build(); diff --git a/web/src/main/java/org/phoenixctms/ctsms/web/util/DefaultSettings.java b/web/src/main/java/org/phoenixctms/ctsms/web/util/DefaultSettings.java index 057358245c9a..ba8ad4d6885a 100644 --- a/web/src/main/java/org/phoenixctms/ctsms/web/util/DefaultSettings.java +++ b/web/src/main/java/org/phoenixctms/ctsms/web/util/DefaultSettings.java @@ -53,7 +53,7 @@ public final class DefaultSettings { public static final int API_DUTYROSTER_ICS_PAST_DAYS = 365; public static final int API_DUTYROSTER_ICS_FUTURE_DAYS = 730; public static final Long API_DUTYROSTER_ICS_JWT_VALIDITY_SECS = null; - public static final boolean DUTY_ROSTER_SCHEDULE_SHOW_GOOGLE_CALENDAR_URL = true; + public final static String DUTY_ROSTER_GOOGLE_CALENDAR_ICS_BASE_URL = ""; public final static String API_VERSION = "0.0.0"; public static final boolean ENABLE_TOOLTIPS = true; public static final boolean TRIAL_STATUS_UPDATE_REQUIRES_PASSWORD = true; diff --git a/web/src/main/java/org/phoenixctms/ctsms/web/util/SettingCodes.java b/web/src/main/java/org/phoenixctms/ctsms/web/util/SettingCodes.java index 6bdf0be9bdfe..94a7dbca6a93 100644 --- a/web/src/main/java/org/phoenixctms/ctsms/web/util/SettingCodes.java +++ b/web/src/main/java/org/phoenixctms/ctsms/web/util/SettingCodes.java @@ -503,7 +503,7 @@ public interface SettingCodes { public static final String API_DUTYROSTER_ICS_PAST_DAYS = "api_dutyroster_ics_past_days"; public static final String API_DUTYROSTER_ICS_FUTURE_DAYS = "api_dutyroster_ics_future_days"; public static final String API_DUTYROSTER_ICS_JWT_VALIDITY_SECS = "api_dutyroster_ics_jwt_validity_secs"; - public static final String DUTY_ROSTER_SCHEDULE_SHOW_GOOGLE_CALENDAR_URL = "duty_roster_schedule_show_google_calendar_url"; + public static final String DUTY_ROSTER_GOOGLE_CALENDAR_ICS_BASE_URL = "duty_roster_google_calendar_ics_base_url"; public static final String API_VERSION = "api_version"; public static final String INPUT_FIELD_DELTA_SUMMARY_MAX = "input_field_delta_summary_max"; public static final String FIELD_CALCULATION_DEBUG_LEVEL = "field_calculation_debug_level"; diff --git a/web/src/main/java/org/phoenixctms/ctsms/web/util/WebUtil.java b/web/src/main/java/org/phoenixctms/ctsms/web/util/WebUtil.java index 8f1554985ed7..a58883d8651c 100644 --- a/web/src/main/java/org/phoenixctms/ctsms/web/util/WebUtil.java +++ b/web/src/main/java/org/phoenixctms/ctsms/web/util/WebUtil.java @@ -60,7 +60,6 @@ import org.phoenixctms.ctsms.vo.*; import org.phoenixctms.ctsms.web.model.ApplicationScopeBean; import org.phoenixctms.ctsms.web.model.SessionScopeBean; -import org.phoenixctms.ctsms.web.jersey.resource.trial.DutyRosterTurnResource; import org.phoenixctms.ctsms.web.util.Settings.Bundle; import org.primefaces.context.RequestContext; import org.primefaces.model.DefaultStreamedContent; @@ -4314,22 +4313,31 @@ public static StaffOutVO getUserIdentity() { return null; } + public static boolean isDutyRosterIcsRailEnabled() { + return !CommonUtil.isEmptyString(getDutyRosterGoogleCalendarIcsBaseUrl()); + } + public static boolean isShowDutyRosterGoogleCalendarUrl() { - return Settings.getBoolean(SettingCodes.DUTY_ROSTER_SCHEDULE_SHOW_GOOGLE_CALENDAR_URL, Bundle.SETTINGS, DefaultSettings.DUTY_ROSTER_SCHEDULE_SHOW_GOOGLE_CALENDAR_URL) - && getUserIdentity() != null; + return getUserIdentity() != null && isDutyRosterIcsRailEnabled(); } public static String getDutyRosterGoogleCalendarIcsUrl() { String jwt = issueDutyRosterIcsJwt(); - if (CommonUtil.isEmptyString(jwt)) { + String baseUrl = getDutyRosterGoogleCalendarIcsBaseUrl(); + if (CommonUtil.isEmptyString(jwt) || CommonUtil.isEmptyString(baseUrl)) { return ""; } - StringBuilder sb = new StringBuilder(getHttpBaseUrl()); - sb.append("/").append(REST_API_PATH).append(DutyRosterTurnResource.ICS_PATH); - sb.append("?").append(JWT_QUERY_PARAM).append("=").append(urlEncodeUtf8(jwt)); + StringBuilder sb = new StringBuilder(baseUrl); + sb.append(baseUrl.indexOf('?') >= 0 ? '&' : '?'); + sb.append(JWT_QUERY_PARAM).append("=").append(urlEncodeUtf8(jwt)); return sb.toString(); } + private static String getDutyRosterGoogleCalendarIcsBaseUrl() { + String baseUrl = Settings.getString(SettingCodes.DUTY_ROSTER_GOOGLE_CALENDAR_ICS_BASE_URL, Bundle.SETTINGS, DefaultSettings.DUTY_ROSTER_GOOGLE_CALENDAR_ICS_BASE_URL); + return baseUrl == null ? "" : baseUrl.trim(); + } + private static String issueDutyRosterIcsJwt() { AuthenticationVO sessionAuth = getAuthentication(); if (sessionAuth == null) { diff --git a/web/src/main/resources/org/phoenixctms/ctsms/web/settings.properties b/web/src/main/resources/org/phoenixctms/ctsms/web/settings.properties index f010d566cab1..9c56ec2e64e1 100644 --- a/web/src/main/resources/org/phoenixctms/ctsms/web/settings.properties +++ b/web/src/main/resources/org/phoenixctms/ctsms/web/settings.properties @@ -40,7 +40,8 @@ api_dutyroster_ics_future_days=730 # null omits the JWT exp claim, so a Google Calendar subscribe URL stays valid until the password or department key changes. api_dutyroster_ics_jwt_validity_secs=null -duty_roster_schedule_show_google_calendar_url=true +# Public Google Calendar subscribe base URL. Empty hides the links. Example: https://www.phoenixctms.org/medunigraz-ics.php +duty_roster_google_calendar_ics_base_url= enable_tooltips=true