use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class WorkingHours method getRecurrenceForDay.
private static IRecurrence getRecurrenceForDay(int dayOfWeek, StartSpec startSpec, TimeRange timeRange, ICalTimeZone tz, TimeZoneMap tzmap) throws ServiceException {
// DTSTART
String dateStr = startSpec.getDateString(dayOfWeek);
String dtStartStr;
if (tz.sameAsUTC())
dtStartStr = String.format("%sT%02d%02d00Z", dateStr, timeRange.start.hour, timeRange.start.minute);
else
dtStartStr = String.format("TZID=\"%s\":%sT%02d%02d00", tz.getID(), dateStr, timeRange.start.hour, timeRange.start.minute);
ParsedDateTime dtStart;
try {
dtStart = ParsedDateTime.parse(dtStartStr, tzmap);
} catch (ParseException e) {
throw ServiceException.INVALID_REQUEST("Bad date/time value \"" + dtStartStr + "\"", e);
}
// DURATION
ParsedDuration dur = timeRange.getDuration();
// RRULE
String dayName = DayOfWeekName.lookup(dayOfWeek);
String ruleStr = String.format("FREQ=WEEKLY;INTERVAL=1;BYDAY=%s", dayName);
return new Recurrence.SimpleRepeatingRule(dtStart, dur, new ZRecur(ruleStr, tzmap), null);
}
use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class Message method processInvitesAfterCreate.
/**
* This has to be done as a separate step, after the MailItem has been added, because of foreign key constraints on
* the CalendarItems table.
*/
private void processInvitesAfterCreate(String method, int folderId, boolean applyToCalendar, ParsedMessage pm, List<Invite> invites) throws ServiceException {
if (pm == null) {
throw ServiceException.INVALID_REQUEST("null ParsedMessage while processing invite in message " + mId, null);
}
Account acct = getAccount();
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(acct);
OperationContext octxt = getMailbox().getOperationContext();
ProcessInvitesStatus status = new ProcessInvitesStatus(acct, pm);
status.initAutoAddNew(octxt);
boolean isOrganizerMethod = Invite.isOrganizerMethod(method);
if (isOrganizerMethod && !invites.isEmpty() && status.intendedForMe) {
// Check if the sender is allowed to invite this user. Only do this for invite-type methods,
// namely REQUEST/PUBLISH/CANCEL/ADD/DECLINECOUNTER. REPLY/REFRESH/COUNTER don't undergo
// the check because they are not organizer-to-attendee methods.
String senderEmail;
Account senderAcct = null;
boolean onBehalfOf = false;
boolean canInvite;
AccessManager accessMgr = AccessManager.getInstance();
if (octxt != null && octxt.getAuthenticatedUser() != null) {
onBehalfOf = octxt.isDelegatedRequest(getMailbox());
senderAcct = octxt.getAuthenticatedUser();
senderEmail = senderAcct.getName();
canInvite = accessMgr.canDo(senderAcct, acct, User.R_invite, octxt.isUsingAdminPrivileges());
} else {
senderEmail = pm.getSenderEmail(false);
if (senderEmail != null) {
senderAcct = Provisioning.getInstance().get(AccountBy.name, senderEmail);
}
canInvite = accessMgr.canDo(senderEmail, acct, User.R_invite, false);
}
if (!canInvite) {
Invite invite = invites.get(0);
CalendarMailSender.handleInviteAutoDeclinedNotification(octxt, getMailbox(), acct, senderEmail, senderAcct, onBehalfOf, applyToCalendar, getId(), invite);
String inviteSender = senderEmail != null ? senderEmail : "unknown sender";
ZimbraLog.calendar.info("Calendar invite from %s to %s is not allowed", inviteSender, acct.getName());
// Turn off auto-add. We still have to run through the code below to save the invite's
// data in message's metadata.
status.autoAddNew = false;
}
}
// Override CLASS property if preference says to mark everything as private.
PrefCalendarApptVisibility prefClass = acct.getPrefCalendarApptVisibility();
boolean forcePrivateClass = prefClass != null && !prefClass.equals(PrefCalendarApptVisibility.public_);
// Ignore alarms set by organizer.
boolean allowOrganizerAlarm = DebugConfig.calendarAllowOrganizerSpecifiedAlarms;
if (calendarItemInfos == null) {
calendarItemInfos = new ArrayList<CalendarItemInfo>();
}
// properties.
if (invites.size() > 1) {
boolean hasSeries = false;
ZOrganizer seriesOrganizer = null;
boolean seriesIsOrganizer = false;
List<ZAttendee> seriesAttendees = null;
ParsedDateTime seriesDtStart = null;
// Get organizer and attendees from series VEVENT.
for (Invite inv : invites) {
if (!inv.hasRecurId()) {
hasSeries = true;
seriesOrganizer = inv.getOrganizer();
seriesIsOrganizer = inv.isOrganizer();
seriesAttendees = inv.getAttendees();
seriesDtStart = inv.getStartTime();
break;
}
}
if (hasSeries) {
for (Invite inv : invites) {
RecurId rid = inv.getRecurId();
if (rid != null) {
if (seriesOrganizer != null && !inv.hasOrganizer()) {
inv.setOrganizer(seriesOrganizer);
inv.setIsOrganizer(seriesIsOrganizer);
// exception instance to have no attendee.
if (!inv.hasOtherAttendees() && seriesAttendees != null) {
for (ZAttendee at : seriesAttendees) {
inv.addAttendee(at);
}
}
}
if (!inv.isAllDayEvent() && seriesDtStart != null) {
// Exchange can send invalid RECURRENCE-ID with HHMMSS set to 000000. Detect it and fix it up
// by copying the time from series DTSTART.
ParsedDateTime ridDt = rid.getDt();
if (ridDt != null && ridDt.hasZeroTime() && !seriesDtStart.hasZeroTime() && ridDt.sameTimeZone(seriesDtStart)) {
ParsedDateTime fixedDt = seriesDtStart.cloneWithNewDate(ridDt);
RecurId fixedRid = new RecurId(fixedDt, rid.getRange());
ZimbraLog.calendar.debug("Fixed up invalid RECURRENCE-ID with zero time; before=[%s], after=[%s]", rid, fixedRid);
inv.setRecurId(fixedRid);
}
}
// Exception instance invites shouldn't point to the same MIME part in the appointment blob
// as the series invite. If they do, we will lose the series attachment when a new exception
// instance update is received.
inv.setMailItemId(0);
}
}
}
}
// used to check if any invite is non-public
boolean publicInvites = true;
status.calItemFolderId = invites.size() > 0 && invites.get(0).isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
CalendarItem firstCalItem = null;
Set<String> calUidsSeen = new HashSet<String>();
for (Invite cur : invites) {
if (!cur.isPublic()) {
publicInvites = false;
}
// it's the correct organizer.
if (!cur.hasOrganizer() && cur.hasOtherAttendees()) {
String fromEmail = pm.getSenderEmail(true);
if (fromEmail != null) {
boolean dangerousSender = false;
// Is sender == recipient? If so, clear attendees.
if (status.intendedForAddress != null) {
if (status.intendedForAddress.equalsIgnoreCase(fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
cur.clearAttendees();
dangerousSender = true;
}
} else if (acctMatcher.matches(fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
cur.clearAttendees();
dangerousSender = true;
}
if (!dangerousSender) {
if (isOrganizerMethod = Invite.isOrganizerMethod(method)) {
// For organizer-originated methods, use email sender as default organizer.
ZOrganizer org = new ZOrganizer(fromEmail, null);
String senderEmail = pm.getSenderEmail(false);
if (senderEmail != null && !senderEmail.equalsIgnoreCase(fromEmail))
org.setSentBy(senderEmail);
cur.setOrganizer(org);
ZimbraLog.calendar.info("Got malformed invite that lists attendees without specifying an organizer. " + "Defaulting organizer to: " + org.toString());
} else {
// For attendee-originated methods, look up organizer from appointment on calendar.
// If appointment is not found, fall back to the intended-for address, then finally to self.
ZOrganizer org = null;
CalendarItem ci = mMailbox.getCalendarItemByUid(octxt, cur.getUid());
if (ci != null) {
Invite inv = ci.getInvite(cur.getRecurId());
if (inv == null) {
inv = ci.getDefaultInviteOrNull();
}
if (inv != null) {
org = inv.getOrganizer();
}
}
if (org == null) {
if (status.intendedForAddress != null) {
org = new ZOrganizer(status.intendedForAddress, null);
} else {
org = new ZOrganizer(acct.getName(), null);
}
}
cur.setOrganizer(org);
cur.setIsOrganizer(status.intendedForMe);
ZimbraLog.calendar.info("Got malformed reply missing organizer. Defaulting to " + org.toString());
}
}
}
}
cur.setLocalOnly(false);
status.initAddRevisionSetting(cur.getUid(), calUidsSeen);
// other than BUSY. And don't allow transparent meetings. This will prevent double booking in the future.
if (cur.isEvent() && (acct instanceof CalendarResource)) {
cur.setFreeBusy(IcalXmlStrMap.FBTYPE_BUSY);
cur.setTransparency(IcalXmlStrMap.TRANSP_OPAQUE);
}
if (forcePrivateClass) {
cur.setClassProp(IcalXmlStrMap.CLASS_PRIVATE);
cur.setClassPropSetByMe(true);
}
ICalTok methodTok = Invite.lookupMethod(method);
// Discard alarms set by organizer. Add a new one based on attendee's preferences.
if (!allowOrganizerAlarm) {
// only for non-cancel/non-declinecounter VEVENTs
if (cur.isEvent() && isOrganizerMethod && !cur.isCancel() && !ICalTok.DECLINECOUNTER.equals(methodTok))
Invite.setDefaultAlarm(cur, acct);
}
getInfoForAssociatedCalendarItem(acct, cur, method, pm, applyToCalendar, status);
if (firstCalItem == null) {
firstCalItem = status.calItem;
}
}
if (status.updatedMetadata) {
saveMetadata();
}
// Don't forward from a system account. (e.g. archiving, galsync, ham/spam)
if (applyToCalendar && !status.isForwardedInvite && status.intendedForMe && folderId != Mailbox.ID_FOLDER_SENT && !invites.isEmpty() && !acct.isIsSystemResource()) {
// Don't do the forwarding during redo playback.
RedoableOp redoPlayer = octxt != null ? octxt.getPlayer() : null;
RedoLogProvider redoProvider = RedoLogProvider.getInstance();
boolean needToForward = redoProvider.isMaster() && (redoPlayer == null || redoProvider.getRedoLogManager().getInCrashRecovery());
if (needToForward) {
String[] forwardTo = null;
if (isOrganizerMethod) {
forwardTo = acct.getPrefCalendarForwardInvitesTo();
} else {
// not the users listed in the zimbraPrefCalendarForwardInvitesTo preference.
if (firstCalItem != null) {
Invite invCalItem = firstCalItem.getInvite(invites.get(0).getRecurId());
if (invCalItem == null)
invCalItem = firstCalItem.getDefaultInviteOrNull();
if (invCalItem != null && invCalItem.isOrganizer()) {
ZOrganizer org = invCalItem.getOrganizer();
if (org.hasSentBy()) {
forwardTo = new String[] { org.getSentBy() };
}
}
}
}
Account senderAcct = null;
String senderEmail = pm.getSenderEmail(false);
if (senderEmail != null)
senderAcct = Provisioning.getInstance().get(AccountBy.name, senderEmail);
if (forwardTo != null && forwardTo.length > 0) {
// recipients to receive unfiltered message
List<String> rcptsUnfiltered = new ArrayList<String>();
// recipients to receive message filtered to remove private data
List<String> rcptsFiltered = new ArrayList<String>();
Folder calFolder = null;
try {
calFolder = getMailbox().getFolderById(status.calItemFolderId);
} catch (NoSuchItemException e) {
ZimbraLog.mailbox.warn("No such calendar folder (" + status.calItemFolderId + ") during invite auto-forwarding");
}
for (String fwd : forwardTo) {
if (fwd != null) {
fwd = fwd.trim();
}
if (StringUtil.isNullOrEmpty(fwd)) {
continue;
}
// Prevent forwarding to self.
if (acctMatcher.matches(fwd))
continue;
// Don't forward back to the sender. It's redundant and confusing.
Account rcptAcct = Provisioning.getInstance().get(AccountBy.name, fwd);
boolean rcptIsSender = false;
if (rcptAcct != null) {
if (senderAcct != null) {
rcptIsSender = rcptAcct.getId().equalsIgnoreCase(senderAcct.getId());
} else {
rcptIsSender = AccountUtil.addressMatchesAccount(rcptAcct, senderEmail);
}
} else {
if (senderAcct != null) {
rcptIsSender = AccountUtil.addressMatchesAccount(senderAcct, fwd);
} else {
rcptIsSender = fwd.equalsIgnoreCase(senderEmail);
}
}
if (rcptIsSender) {
ZimbraLog.calendar.info("Not auto-forwarding to " + fwd + " because it is the sender of this message");
continue;
}
if (publicInvites) {
rcptsUnfiltered.add(fwd);
} else {
boolean allowed = false;
if (calFolder != null && rcptAcct != null) {
allowed = calFolder.canAccess(ACL.RIGHT_PRIVATE, rcptAcct, false);
}
if (allowed) {
rcptsUnfiltered.add(fwd);
} else if (acct instanceof CalendarResource) {
// Forward filtered invite from calendar resource accounts only. Don't forward filtered
// invite from regular user account because the forwardee won't be able to accept/decline
// due to permission error.
rcptsFiltered.add(fwd);
}
}
}
if (!rcptsUnfiltered.isEmpty() || !rcptsFiltered.isEmpty()) {
MimeMessage mmOrig = pm.getMimeMessage();
if (mmOrig != null) {
String origSender = pm.getSenderEmail(false);
String forwarder = AccountUtil.getCanonicalAddress(acct);
if (!rcptsUnfiltered.isEmpty()) {
MimeMessage mm = CalendarMailSender.createForwardedInviteMessage(mmOrig, origSender, forwarder, rcptsUnfiltered.toArray(new String[0]));
if (mm != null) {
ItemId origMsgId = new ItemId(getMailbox(), getId());
CalendarMailSender.sendInviteAutoForwardMessage(octxt, getMailbox(), origMsgId, mm);
}
}
if (!rcptsFiltered.isEmpty()) {
MimeMessage mm = CalendarMailSender.createForwardedPrivateInviteMessage(acct, acct.getLocale(), method, invites, origSender, forwarder, rcptsFiltered.toArray(new String[0]));
if (mm != null) {
ItemId origMsgId = new ItemId(getMailbox(), getId());
CalendarMailSender.sendInviteAutoForwardMessage(octxt, getMailbox(), origMsgId, mm);
}
}
}
}
}
}
}
}
use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class GetCalendarItemSummaries method encodeCalendarItemInstances.
/**
* Encodes a calendar item
*
* @param parent
* @param elementName
* name of element to add (MailConstants .E_APPOINTMENT or MailConstants.E_TASK)
* @param rangeStart
* start period to expand instances (or -1 for no start time constraint)
* @param rangeEnd
* end period to expand instances (or -1 for no end time constraint)
* @param newFormat
* temporary HACK - true: SearchRequest, false: GetAppointmentSummaries
* @return
*/
static EncodeCalendarItemResult encodeCalendarItemInstances(ZimbraSoapContext lc, OperationContext octxt, CalendarItem calItem, Account acct, long rangeStart, long rangeEnd, boolean newFormat) throws ServiceException {
EncodeCalendarItemResult toRet = new EncodeCalendarItemResult();
ItemIdFormatter ifmt = new ItemIdFormatter(lc);
Account authAccount = getAuthenticatedAccount(lc);
boolean hidePrivate = !calItem.allowPrivateAccess(authAccount, lc.isUsingAdminPrivileges());
try {
boolean expandRanges;
if (calItem instanceof Task) {
expandRanges = true;
if (rangeStart == -1 && rangeEnd == -1) {
rangeStart = Long.MIN_VALUE;
rangeEnd = Long.MAX_VALUE;
}
} else {
expandRanges = (rangeStart != -1 && rangeEnd != -1 && rangeStart < rangeEnd);
}
boolean isAppointment = calItem instanceof Appointment;
// Use the marshalling code in calendar summary cache for uniform output, when we can.
if (isAppointment && expandRanges) {
CalendarItemData calItemData = CalSummaryCache.reloadCalendarItemOverRange(calItem, rangeStart, rangeEnd);
if (calItemData != null) {
int numInstances = calItemData.getNumInstances();
if (numInstances > 0) {
Element calItemElem = CacheToXML.encodeCalendarItemData(lc, ifmt, calItemData, !hidePrivate, !newFormat);
toRet.element = calItemElem;
toRet.numInstancesExpanded = numInstances;
}
}
return toRet;
}
// But there are other cases (e.g. tasks, no time range) that require the legacy code below.
// don't initialize until we find at least one valid instance
Element calItemElem = null;
Invite defaultInvite = calItem.getDefaultInviteOrNull();
if (defaultInvite == null) {
mLog.info("Could not load defaultinfo for calendar item with id=" + calItem.getId() + " SKIPPING");
return toRet;
}
ParsedDuration defDuration = defaultInvite.getEffectiveDuration();
// events and 1 second for non all-day. (bug 28615)
if (defDuration == null && !defaultInvite.isTodo()) {
if (defaultInvite.isAllDayEvent()) {
defDuration = ParsedDuration.ONE_DAY;
} else {
defDuration = ParsedDuration.ONE_SECOND;
}
}
long defDurationMsecs = 0;
if (defaultInvite.getStartTime() != null && defDuration != null) {
ParsedDateTime s = defaultInvite.getStartTime();
long et = s.add(defDuration).getUtcTime();
defDurationMsecs = et - s.getUtcTime();
}
String defaultFba = null;
if (calItem instanceof Appointment) {
defaultFba = ((Appointment) calItem).getEffectiveFreeBusyActual(defaultInvite, null);
}
String defaultPtSt = calItem.getEffectivePartStat(defaultInvite, null);
AlarmData alarmData = calItem.getAlarmData();
// add all the instances:
int numInRange = 0;
if (expandRanges) {
Collection<CalendarItem.Instance> instances = calItem.expandInstances(rangeStart, rangeEnd, true);
long alarmTime = 0;
long alarmInst = 0;
if (alarmData != null) {
alarmTime = alarmData.getNextAt();
alarmInst = alarmData.getNextInstanceStart();
}
for (CalendarItem.Instance inst : instances) {
try {
InviteInfo invId = inst.getInviteInfo();
Invite inv = calItem.getInvite(invId.getMsgId(), invId.getComponentId());
boolean showAll = !hidePrivate || inv.isPublic();
// figure out which fields are different from the default and put their data here...
ParsedDuration invDuration = inv.getEffectiveDuration();
long instStart = inst.getStart();
// For an instance whose alarm time is within the time range, we must
// include it even if its start time is after the range.
long startOrAlarm = instStart == alarmInst ? alarmTime : instStart;
// events and 1 second for non all-day. (bug 28615)
if (invDuration == null) {
if (inv.isAllDayEvent())
invDuration = ParsedDuration.ONE_DAY;
else
invDuration = ParsedDuration.ONE_SECOND;
}
if (!inst.hasStart() || (startOrAlarm < rangeEnd && invDuration.addToTime(instStart) > rangeStart)) {
numInRange++;
} else {
continue;
}
if (calItemElem == null) {
calItemElem = lc.createElement(isAppointment ? MailConstants.E_APPOINTMENT : MailConstants.E_TASK);
if (showAll) {
// flags and tags
ToXML.recordItemTags(calItemElem, calItem, octxt);
}
// Organizer
if (inv.hasOrganizer()) {
ZOrganizer org = inv.getOrganizer();
org.toXml(calItemElem);
}
calItemElem.addAttribute("x_uid", calItem.getUid());
calItemElem.addAttribute(MailConstants.A_UID, calItem.getUid());
}
Element instElt = calItemElem.addElement(MailConstants.E_INSTANCE);
if (showAll) {
if (isAppointment && inv.isEvent()) {
String instFba = ((Appointment) calItem).getEffectiveFreeBusyActual(inv, inst);
if (instFba != null && (!instFba.equals(defaultFba) || inst.isException()))
instElt.addAttribute(MailConstants.A_APPT_FREEBUSY_ACTUAL, instFba);
}
String instPtSt = calItem.getEffectivePartStat(inv, inst);
if (!defaultPtSt.equals(instPtSt) || inst.isException())
instElt.addAttribute(MailConstants.A_CAL_PARTSTAT, instPtSt);
}
if (inst.hasStart()) {
instElt.addAttribute(MailConstants.A_CAL_START_TIME, instStart);
if (inv.isAllDayEvent())
instElt.addAttribute(MailConstants.A_CAL_TZ_OFFSET, inst.getStartTzOffset());
}
if (inst.isException() && inv.hasRecurId()) {
RecurId rid = inv.getRecurId();
instElt.addAttribute(MailConstants.A_CAL_RECURRENCE_ID_Z, rid.getDtZ());
} else {
instElt.addAttribute(MailConstants.A_CAL_RECURRENCE_ID_Z, inst.getRecurIdZ());
}
if (inst.isException()) {
instElt.addAttribute(MailConstants.A_CAL_IS_EXCEPTION, true);
instElt.addAttribute(MailConstants.A_CAL_INV_ID, ifmt.formatItemId(calItem, inst.getMailItemId()));
instElt.addAttribute(MailConstants.A_CAL_COMPONENT_NUM, inst.getComponentNum());
if (showAll) {
// fragment has already been sanitized...
String frag = inv.getFragment();
if (frag != null && !frag.equals(""))
instElt.addAttribute(MailConstants.E_FRAG, frag, Element.Disposition.CONTENT);
if (inv.getPriority() != null)
instElt.addAttribute(MailConstants.A_CAL_PRIORITY, inv.getPriority());
if (inv.isEvent()) {
if (inv.getFreeBusy() != null)
instElt.addAttribute(MailConstants.A_APPT_FREEBUSY, inv.getFreeBusy());
if (inv.getTransparency() != null)
instElt.addAttribute(MailConstants.A_APPT_TRANSPARENCY, inv.getTransparency());
}
if (inv.isTodo()) {
if (inv.getPercentComplete() != null)
instElt.addAttribute(MailConstants.A_TASK_PERCENT_COMPLETE, inv.getPercentComplete());
}
if (inv.getName() != null)
instElt.addAttribute(MailConstants.A_NAME, inv.getName());
if (inv.getLocation() != null)
instElt.addAttribute(MailConstants.A_CAL_LOCATION, inv.getLocation());
List<String> categories = inv.getCategories();
if (categories != null) {
for (String cat : categories) {
instElt.addElement(MailConstants.E_CAL_CATEGORY).setText(cat);
}
}
Geo geo = inv.getGeo();
if (geo != null)
geo.toXml(instElt);
if (inv.hasOtherAttendees())
instElt.addAttribute(MailConstants.A_CAL_OTHER_ATTENDEES, true);
if (inv.hasAlarm())
instElt.addAttribute(MailConstants.A_CAL_ALARM, true);
}
instElt.addAttribute(MailConstants.A_CAL_ISORG, inv.isOrganizer());
if (inv.isTodo()) {
if (inst.hasEnd()) {
instElt.addAttribute(MailConstants.A_TASK_DUE_DATE, inst.getEnd());
if (inv.isAllDayEvent())
instElt.addAttribute(MailConstants.A_CAL_TZ_OFFSET_DUE, inst.getEndTzOffset());
}
} else {
if (inst.hasStart() && inst.hasEnd()) {
instElt.addAttribute(newFormat ? MailConstants.A_CAL_NEW_DURATION : MailConstants.A_CAL_DURATION, inst.getEnd() - inst.getStart());
}
}
if (inv.getStatus() != null)
instElt.addAttribute(MailConstants.A_CAL_STATUS, inv.getStatus());
if (inv.getClassProp() != null)
instElt.addAttribute(MailConstants.A_CAL_CLASS, inv.getClassProp());
if (inv.isAllDayEvent())
instElt.addAttribute(MailConstants.A_CAL_ALLDAY, true);
if (inv.isDraft())
instElt.addAttribute(MailConstants.A_CAL_DRAFT, true);
if (inv.isNeverSent())
instElt.addAttribute(MailConstants.A_CAL_NEVER_SENT, true);
if (inv.isRecurrence())
instElt.addAttribute(MailConstants.A_CAL_RECUR, true);
} else {
if (inv.isTodo()) {
if (inst.hasEnd()) {
instElt.addAttribute(MailConstants.A_TASK_DUE_DATE, inst.getEnd());
if (inv.isAllDayEvent())
instElt.addAttribute(MailConstants.A_CAL_TZ_OFFSET_DUE, inst.getEndTzOffset());
}
} else {
// the default duration due to daylight savings time transitions.
if (inst.hasStart() && inst.hasEnd() && defDurationMsecs != inst.getEnd() - inst.getStart()) {
instElt.addAttribute(newFormat ? MailConstants.A_CAL_NEW_DURATION : MailConstants.A_CAL_DURATION, inst.getEnd() - inst.getStart());
}
}
}
} catch (MailServiceException.NoSuchItemException e) {
mLog.info("Error could not get instance " + inst.getMailItemId() + "-" + inst.getComponentNum() + " for appt " + calItem.getId(), e);
}
}
// iterate all the instances
}
if (!expandRanges || numInRange > 0) {
// if we found any calItems at all, we have to encode the "Default" data here
boolean showAll = !hidePrivate || defaultInvite.isPublic();
if (calItemElem == null) {
calItemElem = lc.createElement(isAppointment ? MailConstants.E_APPOINTMENT : MailConstants.E_TASK);
calItemElem.addAttribute("x_uid", calItem.getUid());
calItemElem.addAttribute(MailConstants.A_UID, calItem.getUid());
if (showAll) {
// flags and tags
ToXML.recordItemTags(calItemElem, calItem, octxt);
}
// Organizer
if (defaultInvite.hasOrganizer()) {
ZOrganizer org = defaultInvite.getOrganizer();
org.toXml(calItemElem);
}
}
if (showAll) {
if (alarmData != null)
ToXML.encodeAlarmData(calItemElem, calItem, alarmData);
String defaultPriority = defaultInvite.getPriority();
if (defaultPriority != null)
calItemElem.addAttribute(MailConstants.A_CAL_PRIORITY, defaultPriority);
calItemElem.addAttribute(MailConstants.A_CAL_PARTSTAT, defaultPtSt);
if (defaultInvite.isEvent()) {
calItemElem.addAttribute(MailConstants.A_APPT_FREEBUSY, defaultInvite.getFreeBusy());
calItemElem.addAttribute(MailConstants.A_APPT_FREEBUSY_ACTUAL, defaultFba);
calItemElem.addAttribute(MailConstants.A_APPT_TRANSPARENCY, defaultInvite.getTransparency());
}
if (defaultInvite.isTodo()) {
String pctComplete = defaultInvite.getPercentComplete();
if (pctComplete != null)
calItemElem.addAttribute(MailConstants.A_TASK_PERCENT_COMPLETE, pctComplete);
}
calItemElem.addAttribute(MailConstants.A_NAME, defaultInvite.getName());
calItemElem.addAttribute(MailConstants.A_CAL_LOCATION, defaultInvite.getLocation());
List<String> categories = defaultInvite.getCategories();
if (categories != null) {
for (String cat : categories) {
calItemElem.addElement(MailConstants.E_CAL_CATEGORY).setText(cat);
}
}
Geo geo = defaultInvite.getGeo();
if (geo != null)
geo.toXml(calItemElem);
// fragment has already been sanitized...
String fragment = defaultInvite.getFragment();
if (!fragment.equals(""))
calItemElem.addAttribute(MailConstants.E_FRAG, fragment, Element.Disposition.CONTENT);
if (defaultInvite.hasOtherAttendees()) {
calItemElem.addAttribute(MailConstants.A_CAL_OTHER_ATTENDEES, defaultInvite.hasOtherAttendees());
}
if (defaultInvite.hasAlarm()) {
calItemElem.addAttribute(MailConstants.A_CAL_ALARM, defaultInvite.hasAlarm());
}
}
calItemElem.addAttribute(MailConstants.A_CAL_ISORG, defaultInvite.isOrganizer());
calItemElem.addAttribute(MailConstants.A_ID, ifmt.formatItemId(calItem));
calItemElem.addAttribute(MailConstants.A_CAL_INV_ID, ifmt.formatItemId(calItem, defaultInvite.getMailItemId()));
calItemElem.addAttribute(MailConstants.A_CAL_COMPONENT_NUM, defaultInvite.getComponentNum());
calItemElem.addAttribute(MailConstants.A_FOLDER, ifmt.formatItemId(new ItemId(calItem.getMailbox().getAccountId(), calItem.getFolderId())));
calItemElem.addAttribute(MailConstants.A_CAL_STATUS, defaultInvite.getStatus());
calItemElem.addAttribute(MailConstants.A_CAL_CLASS, defaultInvite.getClassProp());
if (!defaultInvite.isTodo())
calItemElem.addAttribute(newFormat ? MailConstants.A_CAL_NEW_DURATION : MailConstants.A_CAL_DURATION, defDurationMsecs);
if (defaultInvite.isAllDayEvent())
calItemElem.addAttribute(MailConstants.A_CAL_ALLDAY, defaultInvite.isAllDayEvent());
if (defaultInvite.isDraft())
calItemElem.addAttribute(MailConstants.A_CAL_DRAFT, defaultInvite.isDraft());
if (defaultInvite.isNeverSent())
calItemElem.addAttribute(MailConstants.A_CAL_NEVER_SENT, defaultInvite.isNeverSent());
if (defaultInvite.isRecurrence())
calItemElem.addAttribute(MailConstants.A_CAL_RECUR, defaultInvite.isRecurrence());
if (calItem.hasExceptions()) {
calItemElem.addAttribute(MailConstants.A_CAL_HAS_EXCEPTIONS, true);
}
toRet.element = calItemElem;
}
toRet.numInstancesExpanded = numInRange;
} catch (MailServiceException.NoSuchItemException e) {
mLog.info("Error could not get default invite for calendar item: " + calItem.getId(), e);
} catch (RuntimeException e) {
mLog.info("Caught Exception " + e + " while getting summary info for calendar item: " + calItem.getId(), e);
}
return toRet;
}
use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class FriendlyCalendaringDescription method addEventDetails.
private void addEventDetails(Invite invite) {
if (!invite.isEvent())
return;
int origPlainLen = mPlainText.length();
try {
String uid = invite.getUid();
String method = invite.getMethod();
List<ZAttendee> attendees = invite.getAttendees();
Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(mAccount);
// Would be nice to be able to discover what modifications were made by the CalDAV
// client. Currently, this information is not stored in the Calendar.
// Might be able to extract the information from messages in the Sent folder at
// some point in the future?
CalendarItem calItem = mbox.getCalendarItemByUid(null, uid);
if (calItem != null) {
Invite[] calInvites = calItem.getInvites();
if ((calInvites != null) && (calInvites.length > 1))
// At least 1 exception, so this is not a new item
mDefinitelyModified = true;
}
L10nUtil.MsgKey hdrKey = null;
if (method.equals("REQUEST")) {
if (invite.getRecurId() != null)
hdrKey = L10nUtil.MsgKey.zsApptInstanceModified;
else if (mDefinitelyModified)
hdrKey = L10nUtil.MsgKey.zsApptModified;
else
hdrKey = L10nUtil.MsgKey.zsApptNew;
} else if (method.equals("CANCEL")) {
if (invite.getRecurId() != null)
hdrKey = L10nUtil.MsgKey.calendarCancelAppointmentInstance;
else
hdrKey = L10nUtil.MsgKey.calendarCancelAppointment;
} else if (method.equals("REPLY")) {
if ((attendees != null) && !attendees.isEmpty()) {
ZAttendee replier = attendees.get(0);
String partStat = replier.getPartStat();
String replierName = replier.getFriendlyAddress().toString();
if (partStat.equals(IcalXmlStrMap.PARTSTAT_ACCEPTED)) {
hdrKey = L10nUtil.MsgKey.calendarDefaultReplyAccept;
} else if (partStat.equals(IcalXmlStrMap.PARTSTAT_TENTATIVE)) {
hdrKey = L10nUtil.MsgKey.calendarDefaultReplyAccept;
} else if (partStat.equals(IcalXmlStrMap.PARTSTAT_DECLINED)) {
hdrKey = L10nUtil.MsgKey.calendarDefaultReplyDecline;
}
if (hdrKey != null) {
mPlainText.append(L10nUtil.getMessage(hdrKey, mLc, replierName)).append("\n\n");
mHtml.append("<h3>").append(L10nUtil.getMessage(hdrKey, mLc, replierName)).append("</h3>");
hdrKey = null;
}
}
}
if (hdrKey != null) {
mPlainText.append(L10nUtil.getMessage(hdrKey, mLc)).append("\n\n");
mHtml.append("<h3>").append(L10nUtil.getMessage(hdrKey, mLc)).append("</h3>");
}
mHtml.append("\n\n<p>\n<table border=\"0\">\n");
addSimpleRow(L10nUtil.MsgKey.zsSubject, invite.getName());
if (invite.hasOrganizer())
addSimpleRow(L10nUtil.MsgKey.zsOrganizer, invite.getOrganizer().getFriendlyAddress().toString());
mHtml.append("</table>\n<p>\n<table border=\"0\">\n");
addSimpleRow(L10nUtil.MsgKey.zsLocation, invite.getLocation());
ParsedDateTime startTime = invite.getStartTime();
if (startTime != null) {
ParsedDateTime endTime = invite.getEndTime();
if (endTime == null) {
ParsedDuration dur = invite.getDuration();
if (dur == null) {
dur = ParsedDuration.parse(false, 0, 0, 1, /* hours */
0, /* mins */
0);
}
endTime = startTime.add(dur);
}
addSimpleRow(L10nUtil.MsgKey.zsTime, getTimeDisplayString(startTime, endTime, invite.isRecurrence(), invite.isAllDayEvent()));
ZRecur zr = getRecur(invite);
if (zr != null) {
addSimpleRow(L10nUtil.MsgKey.zsRecurrence, getRecurrenceDisplayString(zr, startTime.getCalendarCopy(), mLc));
}
}
if (!method.equals("REPLY") && (attendees != null) && !attendees.isEmpty()) {
mHtml.append("</table>\n<p>\n<table border=\"0\">\n");
StringBuilder attendeeList = new StringBuilder();
boolean firstAdded = false;
for (ZAttendee attendee : attendees) {
if (firstAdded) {
attendeeList.append(", ");
} else {
firstAdded = true;
}
attendeeList.append(attendee.getFriendlyAddress().toString());
}
addSimpleRow(L10nUtil.MsgKey.zsInvitees, attendeeList.toString());
}
mPlainText.append("*~*~*~*~*~*~*~*~*~*\n");
mHtml.append("</table>\n");
mHtml.append("<div>*~*~*~*~*~*~*~*~*~*</div><br>\n");
} catch (ServiceException e) {
sLog.debug("Resetting descriptions due to ServiceException", e);
mPlainText.setLength(origPlainLen);
mHtml.setLength(0);
}
}
use of com.zimbra.common.calendar.ParsedDateTime in project zm-mailbox by Zimbra.
the class Invite method decodeMetadata.
/**
* This API is public for RedoLogging to call into it -- you probably don't want to call it from
* anywhere else!
*
* @param mailboxId
* @param meta
* @param calItem
* @param accountTZ
* @return
* @throws ServiceException
*/
public static Invite decodeMetadata(int mailboxId, Metadata meta, CalendarItem calItem, ICalTimeZone accountTZ) throws ServiceException {
byte btype = (byte) meta.getLong(FN_ITEMTYPE, -1);
MailItem.Type type = btype >= 0 ? MailItem.Type.of(btype) : MailItem.Type.APPOINTMENT;
String uid = meta.get(FN_UID, null);
int mailItemId = (int) meta.getLong(FN_INVMSGID);
int componentNum = (int) meta.getLong(FN_COMPNUM);
String classProp = meta.get(FN_CLASS, IcalXmlStrMap.CLASS_PUBLIC);
boolean classPropSetByMe = meta.getBool(FN_CLASS_SETBYME, false);
String status = meta.get(FN_STATUS, IcalXmlStrMap.STATUS_CONFIRMED);
String freebusy = meta.get(FN_APPT_FREEBUSY, null);
String transp = meta.get(FN_TRANSP, IcalXmlStrMap.TRANSP_OPAQUE);
boolean sentByMe = meta.getBool(FN_SENTBYME);
String fragment = meta.get(FN_FRAGMENT, "");
// default to false for backward compat
boolean descInMeta = meta.getBool(FN_DESC_IN_META, false);
String desc = descInMeta ? meta.get(FN_DESC, null) : null;
String descHtml = descInMeta ? meta.get(FN_DESC_HTML, null) : null;
long completed = meta.getLong(FN_COMPLETED, 0);
ParsedDateTime dtStart = null;
ParsedDateTime dtEnd = null;
ParsedDuration duration = null;
RecurId recurrenceId = null;
TimeZoneMap tzMap = Util.decodeFromMetadata(meta.getMap(FN_TZMAP), accountTZ);
Metadata metaRecur = meta.getMap(FN_RECURRENCE, true);
Recurrence.IRecurrence recurrence = null;
if (metaRecur != null) {
recurrence = Recurrence.decodeMetadata(metaRecur, tzMap);
}
String methodStr = meta.get(FN_METHOD, ICalTok.PUBLISH.toString());
if (ICalTok.CANCEL.toString().equals(methodStr))
status = IcalXmlStrMap.STATUS_CANCELLED;
int flags = (int) meta.getLong(FN_APPT_FLAGS, 0);
try {
// DtStart
dtStart = ParsedDateTime.parse(meta.get(FN_START, null), tzMap);
// DtEnd
dtEnd = ParsedDateTime.parse(meta.get(FN_END, null), tzMap);
if ((flags & APPT_FLAG_ALLDAY) != 0) {
// Fixup historic data with incorrect all-day start/end format.
if (dtStart != null)
dtStart.forceDateOnly();
if (dtEnd != null)
dtEnd.forceDateOnly();
}
// Duration
duration = ParsedDuration.parse(meta.get(FN_DURATION, null));
if (meta.containsKey(FN_RECUR_ID)) {
Metadata rdata = meta.getMap(FN_RECUR_ID);
recurrenceId = RecurId.decodeMetadata(rdata, tzMap);
}
} catch (ParseException e) {
throw ServiceException.FAILURE(String.format("Error parsing metadata for invite %s-%s in calItem %s", mailItemId, componentNum, (calItem != null) ? Integer.toString(calItem.getId()) : "(null)"), e);
}
String name = meta.get(FN_NAME, "");
String loc = meta.get(FN_LOCATION, null);
// For existing invites with no partstat, default to ACCEPTED status.
String partStat = meta.get(FN_PARTSTAT, IcalXmlStrMap.PARTSTAT_ACCEPTED);
// For existing invites with no RSVP, default to true.
boolean rsvp = meta.getBool(FN_RSVP, true);
long dtstamp = meta.getLong(FN_DTSTAMP, 0);
long lastModified = meta.getLong(FN_LAST_MODIFIED, 0);
int seqno = (int) meta.getLong(FN_SEQ_NO, 0);
int lastFullSeqno = (int) meta.getLong(FN_LAST_FULL_SEQ_NO, seqno);
ZOrganizer org = null;
try {
Metadata metaOrg = meta.getMap(FN_ORGANIZER, true);
org = metaOrg != null ? new ZOrganizer(metaOrg) : null;
} catch (ServiceException e) {
sLog.warn("Problem decoding organizer for calItem %s invite %s-%s", (calItem != null) ? Integer.toString(calItem.getId()) : "(null)", mailItemId, componentNum);
}
long numAts = meta.getLong(FN_NUM_ATTENDEES, 0);
ArrayList<ZAttendee> attendees = new ArrayList<ZAttendee>((int) numAts);
for (int i = 0; i < numAts; i++) {
try {
Metadata metaAttendee = meta.getMap(FN_ATTENDEE + i, true);
if (metaAttendee != null)
attendees.add(new ZAttendee(metaAttendee));
} catch (ServiceException e) {
ZimbraLog.calendar.warn("Problem decoding attendee %s for calendar item %s invite %s-%s", i, (calItem != null) ? Integer.toString(calItem.getId()) : "(null)", mailItemId, componentNum);
}
}
boolean isOrganizer = false;
if (meta.containsKey(FN_IS_ORGANIZER)) {
isOrganizer = meta.getBool(FN_IS_ORGANIZER);
} else {
// backward compat for invites created before FN_IS_ORGANIZER was introduced
if (org != null) {
String orgAddr = org.getAddress();
Account account = MailboxManager.getInstance().getMailboxById(mailboxId).getAccount();
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(account);
isOrganizer = acctMatcher.matches(orgAddr);
} else {
// If there are other attendees, it's an Outlook POP/IMAP bug. If not,
// it's a properly formatted single-user event. See isOrganizer()
// method for more info.
isOrganizer = numAts < 1;
}
}
String priority = meta.get(FN_PRIORITY, null);
String pctComplete = meta.get(FN_PCT_COMPLETE, null);
List<String> comments = null;
int numComm = (int) meta.getLong(FN_NUM_COMMENTS, 0);
if (numComm > 0) {
comments = new ArrayList<String>(numComm);
for (int i = 0; i < numComm; i++) {
String comm = meta.get(FN_COMMENT + i, null);
if (comm != null)
comments.add(comm);
}
}
List<String> contacts = null;
int numContacts = (int) meta.getLong(FN_NUM_CONTACTS, 0);
if (numContacts > 0) {
contacts = new ArrayList<String>(numContacts);
for (int i = 0; i < numContacts; i++) {
String contact = meta.get(FN_CONTACT + i, null);
if (contact != null)
contacts.add(contact);
}
}
List<String> categories = null;
int numCat = (int) meta.getLong(FN_NUM_CATEGORIES, 0);
if (numCat > 0) {
categories = new ArrayList<String>(numCat);
for (int i = 0; i < numCat; i++) {
String cat = meta.get(FN_CATEGORY + i, null);
if (cat != null)
categories.add(cat);
}
}
Geo geo = null;
Metadata metaGeo = meta.getMap(FN_GEO, true);
if (metaGeo != null)
geo = Util.decodeGeoFromMetadata(metaGeo);
String url = meta.get(FN_URL, null);
Invite invite = new Invite(type, methodStr, tzMap, calItem, uid, status, priority, pctComplete, completed, freebusy, transp, classProp, dtStart, dtEnd, duration, recurrence, isOrganizer, org, attendees, name, loc, flags, partStat, rsvp, recurrenceId, dtstamp, lastModified, seqno, lastFullSeqno, mailboxId, mailItemId, componentNum, sentByMe, desc, descHtml, fragment, comments, categories, contacts, geo, url);
// a little hacky, but necessary
invite.mDescInMeta = descInMeta;
invite.setClassPropSetByMe(classPropSetByMe);
long numAlarms = meta.getLong(FN_NUM_ALARMS, 0);
for (int i = 0; i < numAlarms; i++) {
try {
Metadata metaAlarm = meta.getMap(FN_ALARM + i, true);
if (metaAlarm != null) {
Alarm alarm = Alarm.decodeMetadata(metaAlarm);
if (alarm != null)
invite.addAlarm(alarm);
}
} catch (ServiceException e) {
ZimbraLog.calendar.warn("Problem decoding alarm %s for calendar item %s invite %s-%s", i, (calItem != null) ? Integer.toString(calItem.getId()) : "(null)", mailItemId, componentNum, e);
}
}
List<ZProperty> xprops = Util.decodeXPropsFromMetadata(meta);
if (xprops != null) {
for (ZProperty xprop : xprops) {
boolean isHtmlDesc = false;
if (ICalTok.X_ALT_DESC.equals(xprop.getToken())) {
// Backward compat. We used to save X-ALT-DESC property as an x-prop. Now we use it
// for HTML description, when FMTTYPE=text/html.
ZParameter fmttype = xprop.getParameter(ICalTok.FMTTYPE);
if (fmttype != null && MimeConstants.CT_TEXT_HTML.equalsIgnoreCase(fmttype.getValue())) {
isHtmlDesc = true;
invite.mDescHtml = xprop.getValue();
}
}
if (!isHtmlDesc)
invite.addXProp(xprop);
}
}
invite.setDontIndexMimeMessage(meta.getBool(FN_DONT_INDEX_MM, false));
boolean localOnly = meta.getBool(FN_LOCAL_ONLY, false);
invite.setLocalOnly(localOnly);
invite.sanitize(false);
return invite;
}
Aggregations