private String getText(CalendarItem calItem, Invite invite, Locale locale, TimeZone tz) throws ServiceException { DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); dateTimeFormat.setTimeZone(tz); DateFormat onlyDateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale); onlyDateFormat.setTimeZone(tz); String formattedStart; String formattedEnd; if (calItem.getType() == MailItem.Type.APPOINTMENT) { Date start = new Date(new Long(getProperty(NEXT_INST_START_PROP_NAME))); formattedStart = dateTimeFormat.format(start); Date end = invite.getEffectiveDuration().addToDate(start); formattedEnd = dateTimeFormat.format(end); } else { // start date and due date is optional for tasks formattedStart = invite.getStartTime() == null ? "" : onlyDateFormat.format(invite.getStartTime().getDate()); formattedEnd = invite.getEndTime() == null ? "" : onlyDateFormat.format(invite.getEndTime().getDate()); } String location = invite.getLocation(); if (StringUtil.isNullOrEmpty(location)) location = L10nUtil.getMessage(L10nUtil.MsgKey.noLocation); String organizer = null; ZOrganizer zOrganizer = invite.getOrganizer(); if (zOrganizer != null) organizer = zOrganizer.hasCn() ? zOrganizer.getCn() : zOrganizer.getAddress(); if (organizer == null) organizer = ""; String folder = calItem.getMailbox().getFolderById(null, calItem.getFolderId()).getName(); return L10nUtil.getMessage( calItem.getType() == MailItem.Type.APPOINTMENT ? L10nUtil.MsgKey.apptReminderSmsText : L10nUtil.MsgKey.taskReminderSmsText, locale, calItem.getSubject(), formattedStart, formattedEnd, location, organizer, folder); }
@Override protected void sendReminder(CalendarItem calItem, Invite invite) throws Exception { Account account = calItem.getAccount(); Locale locale = account.getLocale(); TimeZone tz = Util.getAccountTimeZone(account); MimeMessage mm = new Mime.FixedMimeMessage(JMSession.getSmtpSession(account)); String to = account.getAttr(Provisioning.A_zimbraCalendarReminderDeviceEmail); if (to == null) { ZimbraLog.scheduler.info( "Unable to send calendar reminder sms since %s is not set", Provisioning.A_zimbraCalendarReminderDeviceEmail); return; } mm.setRecipient(javax.mail.Message.RecipientType.TO, new JavaMailInternetAddress(to)); mm.setText(getText(calItem, invite, locale, tz), MimeConstants.P_CHARSET_UTF8); mm.saveChanges(); MailSender mailSender = calItem.getMailbox().getMailSender(); mailSender.setSaveToSent(false); mailSender.sendMimeMessage(null, calItem.getMailbox(), mm); }
static SetCalendarItemData getSetCalendarItemData( ZimbraSoapContext zsc, OperationContext octxt, Account acct, Mailbox mbox, Element e, ParseMimeMessage.InviteParser parser) throws ServiceException { String partStatStr = e.getAttribute(MailConstants.A_CAL_PARTSTAT, IcalXmlStrMap.PARTSTAT_NEEDS_ACTION); // <M> Element msgElem = e.getElement(MailConstants.E_MSG); // check to see whether the entire message has been uploaded under separate cover String attachmentId = msgElem.getAttribute(MailConstants.A_ATTACHMENT_ID, null); Element contentElement = msgElem.getOptionalElement(MailConstants.E_CONTENT); InviteParserResult ipr = null; MimeMessage mm = null; if (attachmentId != null) { ParseMimeMessage.MimeMessageData mimeData = new ParseMimeMessage.MimeMessageData(); mm = SendMsg.parseUploadedMessage(zsc, attachmentId, mimeData); } else if (contentElement != null) { mm = ParseMimeMessage.importMsgSoap(msgElem); } else { CalSendData dat = handleMsgElement(zsc, octxt, msgElem, acct, mbox, parser); mm = dat.mMm; ipr = parser.getResult(); } if (ipr == null && msgElem.getOptionalElement(MailConstants.E_INVITE) != null) { ipr = parser.parse(zsc, octxt, mbox.getAccount(), msgElem.getElement(MailConstants.E_INVITE)); // Get description texts out of the MimeMessage and set in the parsed invite. Do it only if // the MimeMessage has text parts. This prevents discarding texts when they're specified only // in the <inv> but not in mime parts. if (ipr != null && ipr.mInvite != null && mm != null) { String desc = Invite.getDescription(mm, MimeConstants.CT_TEXT_PLAIN); String descHtml = Invite.getDescription(mm, MimeConstants.CT_TEXT_HTML); if ((desc != null && desc.length() > 0) || (descHtml != null && descHtml.length() > 0)) ipr.mInvite.setDescription(desc, descHtml); } } ParsedMessage pm = new ParsedMessage(mm, mbox.attachmentsIndexingEnabled()); Invite inv = (ipr == null ? null : ipr.mInvite); if (inv == null || inv.getDTStamp() == -1) { // zdsync if -1 for 4.5 back compat CalendarPartInfo cpi = pm.getCalendarPartInfo(); ZVCalendar cal = null; if (cpi != null && CalendarItem.isAcceptableInvite(mbox.getAccount(), cpi)) cal = cpi.cal; if (cal == null) throw ServiceException.FAILURE("SetCalendarItem could not build an iCalendar object", null); boolean sentByMe = false; // not applicable in the SetCalendarItem case Invite iCalInv = Invite.createFromCalendar(acct, pm.getFragment(), cal, sentByMe).get(0); if (inv == null) { inv = iCalInv; } else { inv.setDtStamp(iCalInv.getDTStamp()); // zdsync inv.setFragment(iCalInv.getFragment()); // zdsync } } inv.setPartStat(partStatStr); SetCalendarItemData sadata = new SetCalendarItemData(); sadata.invite = inv; sadata.message = pm; return sadata; }
@Override public Element handle(Element request, Map<String, Object> context) throws ServiceException { ZimbraSoapContext zsc = getZimbraSoapContext(context); Mailbox mbox = getRequestedMailbox(zsc); OperationContext octxt = getOperationContext(zsc, context); ItemIdFormatter ifmt = new ItemIdFormatter(zsc); int flags = Flag.toBitmask(request.getAttribute(MailConstants.A_FLAGS, null)); String[] tags = TagUtil.parseTags(request, mbox, octxt); mbox.lock.lock(); try { int defaultFolder = getItemType() == MailItem.Type.TASK ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR; String defaultFolderStr = Integer.toString(defaultFolder); String folderIdStr = request.getAttribute(MailConstants.A_FOLDER, defaultFolderStr); ItemId iidFolder = new ItemId(folderIdStr, zsc); Folder folder = mbox.getFolderById(octxt, iidFolder.getId()); SetCalendarItemParseResult parsed = parseSetAppointmentRequest(request, zsc, octxt, folder, getItemType(), false); CalendarItem calItem = mbox.setCalendarItem( octxt, iidFolder.getId(), flags, tags, parsed.defaultInv, parsed.exceptions, parsed.replies, parsed.nextAlarm); Element response = getResponseElement(zsc); if (parsed.defaultInv != null) { response .addElement(MailConstants.A_DEFAULT) .addAttribute( MailConstants.A_ID, ifmt.formatItemId(parsed.defaultInv.invite.getMailItemId())); } if (parsed.exceptions != null) { for (SetCalendarItemData cur : parsed.exceptions) { Element e = response.addElement(MailConstants.E_CAL_EXCEPT); e.addAttribute(MailConstants.A_CAL_RECURRENCE_ID, cur.invite.getRecurId().toString()); e.addAttribute(MailConstants.A_ID, ifmt.formatItemId(cur.invite.getMailItemId())); } } String itemId = ifmt.formatItemId(calItem == null ? 0 : calItem.getId()); response.addAttribute(MailConstants.A_CAL_ID, itemId); try { Element inv = request .getElement(MailConstants.A_DEFAULT) .getElement(MailConstants.E_MSG) .getElement(MailConstants.E_INVITE); Element comp = inv.getOptionalElement(MailConstants.E_INVITE_COMPONENT); if (comp != null) { inv = comp; } String reqCalItemId = inv.getAttribute(MailConstants.A_CAL_ID); String uid = inv.getAttribute(MailConstants.A_UID); boolean uidSame = (calItem == null || (calItem.getUid() == null && uid == null) || (calItem.getUid() != null && (calItem.getUid().equals(uid) || (Invite.isOutlookUid(calItem.getUid()) && calItem .getUid() .equalsIgnoreCase( uid))))); // new or same as requested, or Outlook and // case-insensitive equal if (ZimbraLog.calendar.isInfoEnabled()) { StringBuilder logBuf = new StringBuilder(); if (!reqCalItemId.equals(itemId)) { logBuf .append("Mapped requested id ") .append(reqCalItemId) .append(" -> ") .append(itemId); } if (!uidSame) { logBuf .append(" ?? requested UID ") .append(uid) .append(" differs from mapped ") .append(calItem.getUid()); ZimbraLog.calendar.warn(logBuf.toString()); } else if (logBuf.length() > 0) { ZimbraLog.calendar.info(logBuf.toString()); } } assert (uidSame); } catch (ServiceException se) { // one of the elements we wanted to use doesn't exist; ignore; no log/assertion possible } if (!parsed.isTodo) response.addAttribute(MailConstants.A_APPT_ID_DEPRECATE_ME, itemId); // for backward compat return response; } finally { mbox.lock.release(); } }
private void compile() throws ServiceException { assert clauses != null; if (clauses.isEmpty()) { return; } // Convert list of Queries into list of QueryOperations, then optimize them. // this generates all of the query operations operation = parseTree.compile(mailbox); ZimbraLog.search.debug("OP=%s", operation); // expand the is:local and is:remote parts into in:(LIST)'s operation = operation.expandLocalRemotePart(mailbox); ZimbraLog.search.debug("AFTEREXP=%s", operation); // optimize the query down operation = operation.optimize(mailbox); ZimbraLog.search.debug("OPTIMIZED=%s", operation); if (operation == null || operation instanceof NoTermQueryOperation) { operation = new NoResultsQueryOperation(); return; } // Use the OperationContext to update the set of visible referenced folders, local AND remote. Set<QueryTarget> targets = operation.getQueryTargets(); assert (operation instanceof UnionQueryOperation || QueryTarget.getExplicitTargetCount(targets) <= 1); // easiest to treat the query two unions: one the LOCAL and one REMOTE parts UnionQueryOperation remoteOps = new UnionQueryOperation(); UnionQueryOperation localOps = new UnionQueryOperation(); if (operation instanceof UnionQueryOperation) { UnionQueryOperation union = (UnionQueryOperation) operation; // separate out the LOCAL vs REMOTE parts... for (QueryOperation op : union.operations) { Set<QueryTarget> set = op.getQueryTargets(); // this assertion OK because we have already distributed multi-target query ops // during the optimize() step assert (QueryTarget.getExplicitTargetCount(set) <= 1); // the assertion above is critical: the code below all assumes // that we only have ONE target (ie we've already distributed if necessary) if (QueryTarget.hasExternalTarget(set)) { remoteOps.add(op); } else { localOps.add(op); } } } else { // single target: might be local, might be remote Set<QueryTarget> set = operation.getQueryTargets(); // this assertion OK because we have already distributed multi-target query ops // during the optimize() step assert (QueryTarget.getExplicitTargetCount(set) <= 1); if (QueryTarget.hasExternalTarget(set)) { remoteOps.add(operation); } else { localOps.add(operation); } } // Handle the REMOTE side: if (!remoteOps.operations.isEmpty()) { // Since optimize() has already been run, we know that each of our ops // only has one target (or none). Find those operations which have // an external target and wrap them in RemoteQueryOperations // iterate backwards so we can remove/add w/o screwing iteration for (int i = remoteOps.operations.size() - 1; i >= 0; i--) { QueryOperation op = remoteOps.operations.get(i); Set<QueryTarget> set = op.getQueryTargets(); // this assertion OK because we have already distributed multi-target query ops // during the optimize() step assert (QueryTarget.getExplicitTargetCount(set) <= 1); // the assertion above is critical: the code below all assumes // that we only have ONE target (ie we've already distributed if necessary) if (QueryTarget.hasExternalTarget(set)) { remoteOps.operations.remove(i); boolean foundOne = false; // find a remoteOp to add this one to for (QueryOperation remoteOp : remoteOps.operations) { if (remoteOp instanceof RemoteQueryOperation) { if (((RemoteQueryOperation) remoteOp).tryAddOredOperation(op)) { foundOne = true; break; } } } if (!foundOne) { RemoteQueryOperation remoteOp = new RemoteQueryOperation(); remoteOp.tryAddOredOperation(op); remoteOps.operations.add(i, remoteOp); } } } // ...we need to call setup on every RemoteQueryOperation we end up with... for (QueryOperation remoteOp : remoteOps.operations) { assert (remoteOp instanceof RemoteQueryOperation); try { ((RemoteQueryOperation) remoteOp).setup(protocol, octxt.getAuthToken(), params); } catch (Exception e) { ZimbraLog.search.info("Ignoring %s during RemoteQuery generation for %s", e, remoteOps); } } } // For the LOCAL parts of the query, do permission checks, do trash/spam exclusion if (!localOps.operations.isEmpty()) { ZimbraLog.search.debug("LOCAL_IN=%s", localOps); Account authAcct = octxt != null ? octxt.getAuthenticatedUser() : mailbox.getAccount(); // Now, for all the LOCAL PARTS of the query, add the trash/spam exclusion part boolean includeTrash = false; boolean includeSpam = false; if (params.inDumpster()) { // Always include trash and spam for dumpster searches. Excluding spam is a client side // choice. includeTrash = true; includeSpam = true; if (!mailbox.hasFullAdminAccess( octxt)) { // If the requester is not an admin, limit to recent date range. long now = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis(); long mdate = now - authAcct.getDumpsterUserVisibleAge(); IntersectionQueryOperation and = new IntersectionQueryOperation(); DBQueryOperation db = new DBQueryOperation(); db.addMDateRange(mdate, false, -1L, false, true); and.addQueryOp((QueryOperation) localOps.clone()); and.addQueryOp(db); localOps.operations.clear(); localOps.operations.add(and); } } else { includeTrash = authAcct.isPrefIncludeTrashInSearch(); ; includeSpam = authAcct.isPrefIncludeSpamInSearch(); } if (!includeTrash || !includeSpam) { // First check that we aren't specifically looking for items in one of these. // For instance, in ZWC, if "Include Shared Items" is selected, the Trash list may currently // use a // search similar to : 'in:"trash" (inid:565 OR is:local)' // where 565 is the folder ID for a shared folder. We don't want to end up doing a search // for items // that are both in "trash" and NOT in "trash"... for (Query q : clauses) { if (q.toString().equalsIgnoreCase("Q(IN:Trash)")) { includeTrash = true; } if (q.toString().equalsIgnoreCase("Q(IN:Junk)")) { includeSpam = true; } } } if (!includeTrash || !includeSpam) { List<QueryOperation> toAdd = new ArrayList<QueryOperation>(); for (Iterator<QueryOperation> iter = localOps.operations.iterator(); iter.hasNext(); ) { QueryOperation cur = iter.next(); if (!cur.hasSpamTrashSetting()) { QueryOperation newOp = cur.ensureSpamTrashSetting(mailbox, includeTrash, includeSpam); if (newOp != cur) { iter.remove(); toAdd.add(newOp); } } } localOps.operations.addAll(toAdd); ZimbraLog.search.debug("LOCAL_AFTER_TRASH/SPAM/DUMPSTER=%s", localOps); } // Check to see if we need to filter out private appointment data boolean allowPrivateAccess = true; if (octxt != null) { allowPrivateAccess = AccessManager.getInstance() .allowPrivateAccess( octxt.getAuthenticatedUser(), mailbox.getAccount(), octxt.isUsingAdminPrivileges()); } // // bug 28892 - ACL.RIGHT_PRIVATE support: // // Basically, if ACL.RIGHT_PRIVATE is set somewhere, and if we're excluding private items from // search, then we need to run the query twice -- once over the whole mailbox with // private items excluded and then UNION it with a second run, this time only in the // RIGHT_PRIVATE enabled folders, with private items enabled. // UnionQueryOperation clonedLocal = null; Set<Folder> hasFolderRightPrivateSet = new HashSet<Folder>(); // ...don't do any of this if they aren't asking for a calendar type... Set<MailItem.Type> types = params.getTypes(); boolean hasCalendarType = types.contains(MailItem.Type.APPOINTMENT) || types.contains(MailItem.Type.TASK); if (hasCalendarType && !allowPrivateAccess && getTextOperationCount(localOps) > 0) { // the searcher is NOT allowed to see private items globally....lets check // to see if there are any individual folders that they DO have rights to... // if there are any, then we'll need to run special searches in those // folders Set<Folder> allVisibleFolders = mailbox.getVisibleFolders(octxt); if (allVisibleFolders == null) { allVisibleFolders = new HashSet<Folder>(); allVisibleFolders.addAll(mailbox.getFolderList(octxt, SortBy.NONE)); } for (Folder f : allVisibleFolders) { if (f.getType() == MailItem.Type.FOLDER && CalendarItem.allowPrivateAccess(f, authAcct, false)) { hasFolderRightPrivateSet.add(f); } } if (!hasFolderRightPrivateSet.isEmpty()) { clonedLocal = (UnionQueryOperation) localOps.clone(); } } Set<Folder> visibleFolders = mailbox.getVisibleFolders(octxt); localOps = handleLocalPermissionChecks(localOps, visibleFolders, allowPrivateAccess); ZimbraLog.search.debug("LOCAL_AFTER_PERM_CHECKS=%s", localOps); if (!hasFolderRightPrivateSet.isEmpty()) { ZimbraLog.search.debug("CLONED_LOCAL_BEFORE_PERM=%s", clonedLocal); // now we're going to setup the clonedLocal tree // to run with private access ALLOWED, over the set of folders // that have RIGHT_PRIVATE (note that we build this list from the visible // folder list, so we are clonedLocal = handleLocalPermissionChecks(clonedLocal, hasFolderRightPrivateSet, true); ZimbraLog.search.debug("CLONED_LOCAL_AFTER_PERM=%s", clonedLocal); // clonedLocal should only have the single INTERSECT in it assert (clonedLocal.operations.size() == 1); QueryOperation optimizedClonedLocal = clonedLocal.optimize(mailbox); ZimbraLog.search.debug("CLONED_LOCAL_AFTER_OPTIMIZE=%s", optimizedClonedLocal); UnionQueryOperation withPrivateExcluded = localOps; localOps = new UnionQueryOperation(); localOps.add(withPrivateExcluded); localOps.add(optimizedClonedLocal); ZimbraLog.search.debug("LOCAL_WITH_CLONED=%s", localOps); // // we should end up with: // // localOps = // UNION(withPrivateExcluded, // UNION(INTERSECT(clonedLocal, // UNION(hasFolderRightPrivateList) // ) // ) // ) // } } UnionQueryOperation union = new UnionQueryOperation(); union.add(localOps); union.add(remoteOps); ZimbraLog.search.debug("BEFORE_FINAL_OPT=%s", union); operation = union.optimize(mailbox); ZimbraLog.search.debug("COMPILED=%s", operation); }