/*
* #dependsFile client/zarafa/core/data/RecordFactory.js
* #dependsFile client/zarafa/core/data/MessageRecord.js
*/
Ext.namespace('Zarafa.calendar');
/**
* @class Zarafa.calendar.AppointmentRecordFields
*
* Array of {@link Ext.data.Field field} configurations for the
* {@link Zarafa.core.data.IPMRecord IPMRecord} object.
* These fields will be available in all 'IPM.Appointment' type messages.
*/
Zarafa.calendar.AppointmentRecordFields = [
{name: 'importance', type: 'int', defaultValue: Zarafa.core.mapi.Importance.NORMAL},
{name: 'sensitivity', type: 'int', defaultValue: Zarafa.core.mapi.Sensitivity.NONE},
{name: 'startdate', type: 'date', dateFormat: 'timestamp', allowBlank : false, sortDir : 'DESC'},
{name: 'duedate', type: 'date', dateFormat: 'timestamp', allowBlank : false, sortDir : 'DESC'},
{name: 'basedate', type: 'date', dateFormat: 'timestamp', defaultValue: null},
{name: 'recurring', type: 'boolean', defaultValue: false},
{name: 'recurring_reset', type: 'boolean', defaultValue: false},
{name: 'recurring_pattern', type: 'string'},
{name: 'startdate_recurring', type: 'date', dateFormat: 'timestamp', defaultValue: null, sortDir : 'DESC'},
{name: 'enddate_recurring', type: 'date', dateFormat: 'timestamp', defaultValue: null, sortDir : 'DESC'},
{name: 'exception', type : 'boolean', defaultValue: false},
{name: 'reply_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
{name: 'reply_name', type: 'string', defaultValue: ''},
{name: 'busystatus', type: 'int', defaultValue: Zarafa.core.mapi.BusyStatus.BUSY},
{name: 'label', type: 'int', defaultValue: Zarafa.core.mapi.AppointmentLabels.NONE},
{name: 'request_sent', type: 'boolean', defaultValue: false},
{name: 'alldayevent', type: 'boolean', defaultValue: false},
{name: 'private', type: 'boolean', defaultValue: false},
{name: 'meeting', type: 'int', defaultValue: Zarafa.core.mapi.MeetingStatus.NONMEETING},
{name: 'location', type: 'string'},
{name: 'duration', type: 'int'},
{name: 'auxiliary_flags', type: 'int'},
{name: 'responsestatus', type: 'int', defaultValue: Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE},
{name: 'reminder', type: 'boolean'},
{name: 'reminder_minutes', type: 'int'},
{name: 'reminder_time', type: 'date', dateFormat: 'timestamp', defaultValue: null},
{name: 'flagdueby', type: 'date', dateFormat: 'timestamp', defaultValue: null},
{name: 'commonstart', type: 'date', dateFormat: 'timestamp', allowBlank : false},
{name: 'commonend', type: 'date', dateFormat: 'timestamp', allowBlank : false},
{name: 'commonassign'},
{name: 'counter_proposal', type: 'boolean', defaultValue: false},
// TODO: Move all Recurrence properties into a single object
{name: 'recurrence_type', mapping: 'type', type: 'int', forceProtocol: true},
{name: 'recurrence_subtype', mapping: 'subtype', type: 'int', forceProtocol: true},
{name: 'recurrence_everyn', mapping: 'everyn', type: 'int', forceProtocol: true},
{name: 'recurrence_regen', mapping: 'regen', type: 'int', forceProtocol: true},
{name: 'recurrence_weekdays', mapping: 'weekdays', type: 'int', forceProtocol: true},
{name: 'recurrence_month', mapping: 'month', type: 'int', forceProtocol: true},
{name: 'recurrence_monthday', mapping: 'monthday', type: 'int', forceProtocol: true},
{name: 'recurrence_nday', mapping: 'nday', type: 'int', forceProtocol: true},
{name: 'recurrence_term', mapping: 'term', type: 'int', forceProtocol: true},
{name: 'recurrence_numoccur', mapping: 'numoccur', type: 'int', forceProtocol: true},
{name: 'recurrence_numexcept', mapping: 'numexcept', type: 'int', forceProtocol: true},
{name: 'recurrence_numexceptmod', mapping: 'numexceptmod', type: 'int', forceProtocol: true},
{name: 'recurrence_start', mapping: 'start', type: 'date', dateFormat: 'timestamp', forceProtocol: true, defaultValue: null},
{name: 'recurrence_end', mapping: 'end', type: 'date', dateFormat: 'timestamp', forceProtocol: true, defaultValue: null},
{name: 'recurrence_startocc', mapping: 'startocc', type: 'int', forceProtocol: true},
{name: 'recurrence_endocc', mapping: 'endocc', type: 'int', forceProtocol: true},
// TODO: Move all Timezone properties into a single object
{name: 'timezone', type: 'int', forceProtocol: true},
{name: 'timezone_unk', mapping: 'unk', type: 'int', forceProtocol: true},
{name: 'timezone_timezonedst', mapping: 'timezonedst', type: 'int', forceProtocol: true},
{name: 'timezone_dstendmonth', mapping: 'dstendmonth', type: 'int', forceProtocol: true},
{name: 'timezone_dstendweek', mapping: 'dstendweek', type: 'int', forceProtocol: true},
{name: 'timezone_dstendday', mapping: 'dstendday', type: 'int', forceProtocol: true},
{name: 'timezone_dstendhour', mapping: 'dstendhour', type: 'int', forceProtocol: true},
{name: 'timezone_dststartmonth', mapping: 'dststartmonth', type: 'int', forceProtocol: true},
{name: 'timezone_dststartweek', mapping: 'dststartweek', type: 'int', forceProtocol: true},
{name: 'timezone_dststartday', mapping: 'dststartday', type: 'int', forceProtocol: true},
{name: 'timezone_dststarthour', mapping: 'dststarthour', type: 'int', forceProtocol: true}
];
/**
* You are programmatically manipulating appointment items in Microsoft Outlook
* and expect the message class of all appointment items to be IPM.Appointment,
* but the message class of recurring exceptions is set to IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}
* http://support.microsoft.com/kb/183024
*
* IPM.OLE.Class : The exception item of a recurrence series
*/
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}', Zarafa.calendar.AppointmentRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}', Zarafa.core.data.MessageRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Appointment', Zarafa.calendar.AppointmentRecordFields);
Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.Appointment', Zarafa.core.data.MessageRecordFields);
Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.Appointment', 'createphantom', Zarafa.core.data.MessageRecordPhantomHandler);
Zarafa.core.data.RecordFactory.addListenerToMessageClass('IPM.Appointment', 'createphantom', function(record, data) {
record.beginEdit();
if (Ext.isNumber(record.get('duration'))) {
var startDate = record.get('startdate') || record.get('commonstart');
var dueDate = record.get('duedate') || record.get('commonend');
if (startDate && dueDate) {
record.set('duration', (dueDate - startDate) / (60 * 1000));
}
}
var settings = container.getSettingsModel();
if (!data || !Ext.isDefined(data.reminder)) {
var reminder = false;
var store = container.getHierarchyStore().getById(record.get('store_entryid'));
if (!store || !store.isPublicStore()) {
reminder = settings.get('zarafa/v1/contexts/calendar/default_reminder');
}
record.set('reminder', reminder);
}
if (!data || !Ext.isDefined(data.reminder_minutes)) {
if (record.get('alldayevent')) {
record.set('reminder_minutes', settings.get('zarafa/v1/contexts/calendar/default_allday_reminder_time'));
} else {
record.set('reminder_minutes', settings.get('zarafa/v1/contexts/calendar/default_reminder_time'));
}
}
if (record.get('reminder')) {
var startDate = record.get('startdate');
if (Ext.isDate(startDate)) {
record.set('reminder_time', startDate);
record.set('flagdueby', startDate.add(Date.MINUTE, -record.get('reminder_minutes')));
}
}
record.endEdit();
});
/**
* @class Zarafa.calendar.AppointmentRecord
* @extends Zarafa.core.data.MessageRecord
*
* An extension to the {@link Zarafa.core.data.MessageRecord MessageRecord} specific to Appointment Request/Response Messages.
*/
Zarafa.calendar.AppointmentRecord = Ext.extend(Zarafa.core.data.MessageRecord, {
/**
* The base array of ID properties which is copied to the {@link #idProperties}
* when the record is being created.
* @property
* @type Array
* @private
*/
baseIdProperties : [ 'entryid', 'store_entryid', 'parent_entryid', 'basedate', 'attach_num' ],
/**
* Compare this {@link Zarafa.core.data.IPMRecord record} instance with another one to see
* if they are the same IPM Message from the server (i.e. The entryid matches).
*
* @param {Zarafa.core.data.IPMRecord} record The IPMRecord to compare with
* @return {Boolean} True if the records are the same.
*/
equals : function(record)
{
if (Zarafa.calendar.AppointmentRecord.superclass.equals.apply(this, arguments) === true) {
// For recurring appointments we must also check if we have a match on the occurence (through the basedate).
return (this.get('basedate') === record.get('basedate'));
}
return false;
},
/**
* Function will get the recurrence information from (@link Zarafa.core.data.IPMRecord IPMRecord) and
* will generate the recurring pattern string that can be saved in recurring_pattern proeprty of {@link Zarafa.core.data.IPMRecord IPMRecord}.
* @return {String} Recurring pattern string that can be saved in {@link Zarafa.core.data.IPMRecord IPMRecord}.
*/
generateRecurringPattern : function()
{
// Start formatting the properties in such a way we can apply
// them directly into the recurrence pattern.
var type = this.get('recurrence_type');
var everyn = this.get('recurrence_everyn');
var start = this.get('recurrence_start').toUTC();
var end = this.get('recurrence_end').toUTC();
var term = this.get('recurrence_term');
var numocc = this.get('recurrence_numoccur');
var startocc = this.get('recurrence_startocc');
var endocc = this.get('recurrence_endocc');
var pattern;
var occSingleDayRank = false;
var occTimeRange = (startocc !== 0 && endocc !== 0);
switch (type) {
case Zarafa.common.recurrence.data.RecurrenceType.DAILY:
if (everyn == 1) {
type = _('workday');
occSingleDayRank = true;
} else if (everyn == (24 * 60)) {
type = _('day');
occSingleDayRank = true;
} else {
everyn /= (24 * 60);
type = _('days');
occSingleDayRank = false;
}
break;
case Zarafa.common.recurrence.data.RecurrenceType.WEEKLY:
if (everyn == 1) {
type = _('week');
occSingleDayRank = true;
} else {
type = _('weeks');
occSingleDayRank = false;
}
// Append selected week days related information, if any
type += _(' on ') + this.prepareWeekDaysString();
break;
case Zarafa.common.recurrence.data.RecurrenceType.MONTHLY:
if (everyn == 1) {
type = _('month');
occSingleDayRank = true;
} else {
type = _('months');
occSingleDayRank = false;
}
break;
case Zarafa.common.recurrence.data.RecurrenceType.YEARLY:
if (everyn <= 12) {
everyn = 1;
type = _('year');
occSingleDayRank = true;
} else {
everyn = everyn / 12;
type = _('years');
occSingleDayRank = false;
}
break;
}
var tmpStart = start.clone();
tmpStart.setHours(startocc / 60);
tmpStart.setMinutes(startocc % 60);
// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
startocc = tmpStart.format(_('G:i'));
tmpStart.setHours(endocc / 60);
tmpStart.setMinutes(endocc % 60);
// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
endocc = tmpStart.format(_('G:i'));
// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
start = start.format(_('d/m/Y'));
// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
end = end.format(_('d/m/Y'));
// Based on the properties, we need to generate the recurrence pattern string.
// This is obviously very easy since we can simply concatenate a bunch of strings,
// however this messes up translations for languages which order their words
// differently.
// To improve translation quality we create a series of default strings, in which
// we only have to fill in the correct variables. The base string is thus selected
// based on the available properties.
if (term == Zarafa.common.recurrence.data.RecurrenceEnd.NEVER) {
if (occTimeRange) {
if (occSingleDayRank) {
pattern = String.format(_('Occurs every {0} effective {1} from {2} to {3}.'), type, start, startocc, endocc);
} else {
pattern = String.format(_('Occurs every {0} {1} effective {2} from {3} to {4}.'), everyn, type, start, startocc, endocc);
}
} else {
if (occSingleDayRank) {
pattern = String.format(_('Occurs every {0} effective {1}.'), type, start);
} else {
pattern = String.format(_('Occurs every {0} {1} effective {2}.'), everyn, type, start);
}
}
} else if (term == Zarafa.common.recurrence.data.RecurrenceEnd.N_OCCURENCES) {
if (occTimeRange) {
if (occSingleDayRank) {
pattern = String.format(ngettext('Occurs every {0} effective {1} for {2} occurence from {3} to {4}.', 'Occurs every {0} effective {1} for {2} occurences from {3} to {4}.', numocc),
type, start, numocc, startocc, endocc);
} else {
pattern = String.format(ngettext('Occurs every {0} {1} effective {2} for {3} occurence from {4} to {5}.', 'Occurs every {0} {1} effective {2} for {3} occurences {4} to {5}.', numocc),
everyn, type, start, numocc, startocc, endocc);
}
} else {
if (occSingleDayRank) {
pattern = String.format(ngettext('Occurs every {0} effective {1} for {2} occurence.', 'Occurs every {0} effective {1} for {2} occurences.', numocc),
type, start, numocc);
} else {
pattern = String.format(ngettext('Occurs every {0} {1} effective {2} for {3} occurence.', 'Occurs every {0} {1} effective {2} for {3} occurences.', numocc),
everyn, type, start, numocc);
}
}
} else if (term == Zarafa.common.recurrence.data.RecurrenceEnd.ON_DATE) {
if (occTimeRange) {
if (occSingleDayRank) {
pattern = String.format(_('Occurs every {0} effective {1} until {2} from {3} to {4}.'), type, start, end, startocc, endocc);
} else {
pattern = String.format(_('Occurs every {0} {1} effective {2} until {3} from {4} to {5}.'), everyn, type, start, end, startocc, endocc);
}
} else {
if (occSingleDayRank) {
pattern = String.format(_('Occurs every {0} effective {1} until {2}.'), type, start, end);
} else {
pattern = String.format(_('Occurs every {0} {1} effective {2} until {3}.'), everyn, type, start, end);
}
}
}
return pattern;
},
/**
* Generates meeting time details which will be added to meeting response body.
* @return {String} generated body message.
*/
generateMeetingTimeInfo : function(responseText)
{
var messageBody = responseText || '';
var startDate = this.get('startdate');
var dueDate = this.get('duedate');
var meetingLocation = this.get('location') || '';
var recurringPattern = this.get('recurring_pattern') || '';
var meetingTimeInfo = _('When') + ': ';
if (recurringPattern) {
meetingTimeInfo += recurringPattern + '\n';
} else {
// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
meetingTimeInfo += startDate.format(_('l jS F Y G:i')) + ' - ';
// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
meetingTimeInfo += dueDate.format(_('l jS F Y G:i')) + '\n';
}
meetingTimeInfo += _('Where') + ': ' + meetingLocation + '\n\n';
meetingTimeInfo += '*~*~*~*~*~*~*~*~*~*\n\n' + messageBody;
return meetingTimeInfo;
},
/**
* Sends a requests to accept/decline a incoming meeting request.
*
* @param {Zarafa.core.mapi.ResponseStatus} responseType accept/decline/propose new time
* @param {String} meetingTimeInfo Meeting Time Information shown in message body.
* @param {Boolean} noResponse true if no response should be send to organizer else false
* @private
*/
sendMeetingRequestResponse : function(responseType, meetingTimeInfo, sendResponse)
{
if (Ext.isDefined(responseType)) {
switch(responseType)
{
case Zarafa.core.mapi.ResponseStatus.RESPONSE_TENTATIVE:
case Zarafa.core.mapi.ResponseStatus.RESPONSE_ACCEPTED:
this.addMessageAction('action_type', 'acceptMeetingRequest');
break;
case Zarafa.core.mapi.ResponseStatus.RESPONSE_DECLINED:
this.addMessageAction('action_type', 'declineMeetingRequest');
break;
}
this.addMessageAction('responseType', responseType);
this.addMessageAction('meetingTimeInfo', meetingTimeInfo);
this.addMessageAction('sendResponse', sendResponse);
this.getStore().save(this);
}
},
/**
* Respond to a meeting request with the correct {@link Zarafa.core.mapi.ResponseStatus}.
*
* @param {Zarafa.core.mapi.ResponseStatus} responseType accept/decline/tentative
* @param {String} comment user's extra comments to response
* @param {Boolean} sendResponse send response to organizer
*/
respondToMeetingRequest : function(responseType, comment, sendResponse)
{
this.sendMeetingRequestResponse(responseType, this.generateMeetingTimeInfo(comment), sendResponse);
},
/**
* Proposes new time for received meeting request
*
* @param {Zarafa.core.mapi.ResponseStatus} responseType tentative accept/decline
* @param {String} comment user's extra comment in propose new time dialog
* @param {Date} startDate Proposed start time for the meeting
* @param {Date} endDate Proposed end time for the meeting
*/
proposeNewTimeToMeetingRequest : function(responseType, comment, startDate, endDate)
{
this.set('counter_proposal', true);
this.addMessageAction('proposed_starttime', startDate);
this.addMessageAction('proposed_endtime', endDate);
this.sendMeetingRequestResponse(responseType, this.generateProposeNewTimeBody(comment, startDate, endDate), true);
},
/**
* Genereates body for propose new time MR mail.
*
* @param {String} comment user's extra comments in propose new time dialog
* @param {Date} startDate Proposed start time for the meeting
* @param {Date} endDate Proposed end time for the meeting
*/
generateProposeNewTimeBody : function (comment, startDate, endDate)
{
var proposeNewTimeBody = comment + '\n\n\n-----------\n' + _('New Meeting Time Proposed') + ':\n';
// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
proposeNewTimeBody += startDate.format(_('l jS F Y G:i')) + ' - ';
// # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions
proposeNewTimeBody += endDate.format(_('l jS F Y G:i')) + '\n';
return proposeNewTimeBody;
},
/**
* Function cancels Meeting invitation and sends Meeting Cancellation message.
* @param {String} Meeting Time Information shown in message body.
*/
cancelInvitation : function(meetingTimeInfo)
{
this.addMessageAction('action_type', 'cancelInvitation');
this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(meetingTimeInfo));
var store = this.getStore();
store.remove(this);
store.save(this);
},
/**
* Function which cancel/decline/deletes Meeting and sends Meeting decline message to organizer.
* @param {String} sendUpdateFlag flag which decides messageAction for the deleting item
*/
declineMeeting : function(sendUpdateFlag)
{
var store = this.getStore();
if(sendUpdateFlag){
this.addMessageAction('action_type', 'declineMeeting');
}
store.remove(this);
store.save(this);
},
/**
* Function is used to check if duedate property value of
* {@link Zarafa.calendar.AppointmentRecord AppointmentRecord} is in past or not.
* @return {Boolean} true if meeting/appointment is in past else false.
*/
isAppointmentInPast : function()
{
var dueDate = this.get('duedate');
// find the correct due date for recurring appointments
if (this.isRecurring()) {
dueDate = this.get('enddate_recurring');
}
if(Ext.isDate(dueDate) && dueDate.getTime() < (new Date().getTime())) {
return true;
}
return false;
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
* been sent to the invitees.
*/
isMeetingSent : function()
{
return this.isMeetingOrganized() && this.get('request_sent') === true;
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
* meeting status received.
*/
isMeetingReceived : function()
{
return this.get('meeting') === Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED &&
this.get('responsestatus') !== Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED;
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
* meeting status organized.
*/
isMeetingOrganized : function()
{
return this.get('meeting') === Zarafa.core.mapi.MeetingStatus.MEETING &&
this.get('responsestatus') === Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED;
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
* meeting status nonmeeting.
*/
isMeeting : function()
{
var meeting = this.get('meeting');
return (meeting && meeting !== Zarafa.core.mapi.MeetingStatus.NONMEETING);
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
* meeting status meeting canceled.
*/
isMeetingCanceled : function()
{
var meeting = this.get('meeting');
return (meeting === Zarafa.core.mapi.MeetingStatus.MEETING_CANCELED || meeting === Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED_AND_CANCELED);
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} has
* meeting status meeting received and canceled.
*/
isMeetingReceivedAndCanceled : function()
{
return this.get('meeting') === Zarafa.core.mapi.MeetingStatus.MEETING_RECEIVED_AND_CANCELED &&
this.get('responsestatus') !== Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED;
},
/**
* @return {Boolean} Returns true when the organizer requires a response from the attendee
*/
isMeetingResponseRequired : function()
{
return this.isMeetingReceived() && this.get('responsestatus') !== Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE;
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} is
* recurring appointment.
*/
isRecurring : function()
{
return this.get('recurring') === true && !this.hasIdProp('basedate');
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} is
* recurring occurence appointment.
*/
isRecurringOccurence : function()
{
return Ext.isDate(this.get('basedate'));
},
/**
* @return {Boolean} Returns true if the {@link Zarafa.core.data.AppointmentRecord AppointmentRecord} is
* recurring occurence appointment.
*/
isRecurringException : function()
{
return Ext.isDate(this.get('basedate')) && this.get('exception') === true;
},
/**
* Convenience method for determining if the message is a sub message of another message.
* For appointment record we need to also check if message is not an exception/occurence of recurring appointment.
* @return {Boolean} True if this message is a sub message.
*/
isSubMessage : function()
{
// Recurring occurences can never be embedded message
if(this.isRecurringOccurence()) {
return false;
}
return Zarafa.calendar.AppointmentRecord.superclass.isSubMessage.apply(this, arguments);
},
/**
* @return {Boolean} Returns true if this
*/
hasRecurringExceptions : function()
{
// The appointment is not open, we don't know if there is an exeption or not
if (!this.isOpened()) {
return undefined;
}
// If the apointment is not recurring, or this is an occurence, there are no exceptions.
if (!this.isRecurring()) {
return false;
}
// If the appointment is recurring and having any exceptions.
if (this.isRecurring() && this.get('recurrence_numexcept') > 0) {
return true;
}
// Search if the recurring appointment has an exception attachment.
var subStore = this.getSubStore('attachments');
if (!subStore) {
return false;
}
return subStore.findBy(function(attach) { return attach.isRecurrenceException(); }) >= 0;
},
/**
* Called by the store after the record was opened successfully.
* @private
*/
afterOpen : function()
{
if (this.isRecurring()) {
// Recurring appointments can be opened as series or as occurence.
// When openening as a series, we actually have all the data for a single
// occurence (the one the user selected from the UI), which we are overriding
// with the data about the series. However, the occurence and series have
// slight differences. The series doesn't have a basedate, and the startdate
// and duedate properties are the values from the first occurence.
// When the record is being opened, it might already have been hooked to a
// UI componennt, which at this moment has been initialized with the data
// from the occurence. Force all fields which will likely to be different
// between the occurence and series to be marked as modified, forcing the
// UI to reinitialize the components which belong to it.
this.modified = this.modified || {};
this.modified['startdate'] = this.get('startdate');
this.modified['duedate'] = this.get('duedate');
this.modified['basedate'] = this.get('basedate');
if (this.trackUpdateModifications === true) {
this.updateModifications = this.updateModifications || {};
this.updateModifications['startdate'] = this.get('startdate');
this.updateModifications['duedate'] = this.get('duedate');
this.updateModifications['basedate'] = this.get('basedate');
}
}
Zarafa.calendar.AppointmentRecord.superclass.afterOpen.call(this);
this.opened = !this.isRecurring();
this.openedSeries = !this.opened;
this.updateMeetingRecipients();
},
/**
* @return {Boolean} true if the record has been fully loaded.
*/
isOpened : function()
{
if (!this.isRecurring()) {
return this.opened === true;
} else {
return this.openedSeries === true;
}
},
/**
* @return {Boolean} true if the record is copied record.
*/
isCopied : function ()
{
return (this.get('auxiliary_flags') & Zarafa.core.mapi.AppointmentAuxiliaryFlags.auxApptFlagCopied) > 0;
},
/**
* Add action to Message Action list. When the added MessageAction is 'Send',
* then this will automatically {@link #generateMeetingTimeInfo update the meetingTimeInfo} as well.
* @param {String} name The action name to add to the list.
* @param {String} value The value attached to the action name
*/
addMessageAction : function(name, value)
{
Zarafa.calendar.AppointmentRecord.superclass.addMessageAction.apply(this, arguments);
if (name === 'send') {
this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(this.getBody()));
}
if (name === 'meetingTimeInfo') {
// If the record has not been opened yet, the body will not be part of the
// meetingTimeInfo. In this case the server needs to add this.
if(!this.isOpened()){
this.addMessageAction('append_body', true);
}else{
this.deleteMessageAction('append_body');
}
}
},
/**
* Called by Extjs whenever {@link #endEdit} has been called (or {@link #set} without {@link #editing} enabled).
* If this {@link #hasMessageAction messageAction} 'send' has been set, then this will automatically
* {@link #generateMeetingTimeInfo update the meetingTimeInfo} as well.
* @override
* @private
*/
afterEdit : function()
{
if (this.isMeeting() && this.hasMessageAction('send')) {
this.addMessageAction('meetingTimeInfo', this.generateMeetingTimeInfo(this.getBody()));
}
Zarafa.calendar.AppointmentRecord.superclass.afterEdit.apply(this, arguments);
},
/**
* This will update all timezone properties inside this record, the properties
* will be initialized according to the timezone information which is currently
* valid for this browser (See {@link Date#getTimezoneStruct}).
*/
updateTimezoneInformation : function()
{
var tz = Date.getTimezoneStruct();
this.beginEdit();
this.set('timezone', tz.timezone);
this.set('timezone_timezonedst', tz.timezonedst);
this.set('timezone_dstendmonth', tz.dstendmonth);
this.set('timezone_dstendweek', tz.dstendweek);
this.set('timezone_dstendday', tz.dstendday);
this.set('timezone_dstendhour', tz.dstendhour);
this.set('timezone_dststartmonth', tz.dststartmonth);
this.set('timezone_dststartweek', tz.dststartweek);
this.set('timezone_dststartday', tz.dststartday);
this.set('timezone_dststarthour', tz.dststarthour);
this.endEdit();
},
/**
* This will update the {@link Zarafa.calendar.AppointmentRecord AppointmentRecord} recipients
* based on the current {@link Zarafa.core.mapi.MeetingStatus 'meeting' status}.
* If this appointment is a {@link #isMeeting meeting} then the organizer will be added into
* the recipients table otherwise all recipients will be removed.
*/
updateMeetingRecipients : function()
{
var recipientStore = this.getSubStore('recipients');
if (!Ext.isDefined(recipientStore)) {
return;
}
if (this.isMeeting()) {
// find index of organizer's record
var orgIndex = recipientStore.findBy(function(record, id) {
if(record.isMeetingOrganizer()) {
return true;
}
}, this);
if (orgIndex < 0) {
var props = {
recipient_type : Zarafa.core.mapi.RecipientType.MAPI_ORIG,
recipient_flags : Zarafa.core.mapi.RecipientFlags.recipSendable | Zarafa.core.mapi.RecipientFlags.recipOrganizer,
recipient_trackstatus : Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED
};
// Check if the sent_representing_* properties are there,
// if that is the case the organizer is the represented user,
// otherwise the real sender is the organizer,
if (this.get('sent_representing_entryid')) {
Ext.apply(props, {
display_name : this.get('sent_representing_name'),
smtp_address : this.get('sent_representing_email_address'),
address_type : this.get('sent_representing_address_type'),
entryid : this.get('sent_representing_entryid')
});
} else {
Ext.apply(props, {
display_name : this.get('sender_name'),
smtp_address : this.get('sender_email_address'),
address_type : this.get('sender_address_type'),
entryid : this.get('sender_entryid')
});
}
// no organizer found then, add one
// Apply fake ID to prevent is being marked as phantom
var organizer = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT, props, -1);
// Act like a dummy store load operation to add organizer in recipient sub store
// without any changes being registered.
var existingRecords = [];
recipientStore.each(function(rec){
// Copy the record to get a separate instance instead of referring the same instance.
existingRecords.push(rec.copy());
});
existingRecords.push(organizer);
recipientStore.loadRecords({records : existingRecords}, undefined, true);
// Register for an update event of ShadowStore to add organizer every time
// when server responded about recipient(s) related changes
container.getShadowStore().on('update', this.addOrganizerAfterUpdate, this);
}
// Apply sorting to the RecipientStore as we want
// the organizer to be sorted to the start of the store,
// and the rest to be applied on the rowid.
recipientStore.sortBy('ASC', function(a, b) {
var aOrganizer = a.isMeetingOrganizer();
var bOrganizer = b.isMeetingOrganizer();
if (aOrganizer !== bOrganizer) {
return aOrganizer ? -1 : 1;
} else {
return a.get('rowid') - b.get('rowid');
}
});
} else if (recipientStore.getCount() > 0) {
// Normal appointments don't have any recipients.
recipientStore.removeAll();
}
},
/**
* Event handler executed when update event will be fired if a Record has been updated.
* {@link #updateMeetingRecipients} method will be called to add organizer into the recipient sub store,
* while server will send modified recipient set.
* @param {Zarafa.core.data.ShadowStore} store The store from which any record(s) gets updated
* @param {Ext.data.Record} record The Record that was updated
* @param {String} operation The update operation being performed
*/
addOrganizerAfterUpdate : function(store, record, operation)
{
if(operation === Ext.data.Record.COMMIT) {
// Unregister the update event from shadow store as it will be registered again if there is no organizer found
container.getShadowStore().un('update', this.addOrganizerAfterUpdate, this);
this.updateMeetingRecipients();
}
},
/**
* Update the current appointment to a meeting, this will update the 'meeting'
* and 'responsestatus' properties to the proper values.
*/
convertToMeeting : function()
{
this.beginEdit();
this.set('meeting', Zarafa.core.mapi.MeetingStatus.MEETING);
this.set('responsestatus', Zarafa.core.mapi.ResponseStatus.RESPONSE_ORGANIZED);
this.updateMeetingRecipients();
this.endEdit();
},
/**
* Update the current meeting to a normal appointment, this will update the 'meeting'
* and 'responsestatus' properties to the proper values.
*/
convertToAppointment : function()
{
this.beginEdit();
this.set('meeting', Zarafa.core.mapi.MeetingStatus.NONMEETING);
this.set('responsestatus', Zarafa.core.mapi.ResponseStatus.RESPONSE_NONE);
this.updateMeetingRecipients();
this.endEdit();
},
/**
* Function is used to convert a series record to an occurence record.
* When we are opening a single occurence from series record then we need to modify some properties
* that are different on occurence record and also need to add basedate as {@link #idProperties}.
* so the record can be correctly identified as an occurence record.
* @return {Zarafa.core.data.IPMRecord} record that can be used to open occurence of a series.
*/
convertToOccurenceRecord : function()
{
// only convert it when its a series record
if(this.hasIdProp('basedate') === false) {
var cloneRec = this.copy();
// For occurences the the 'recurring' property must
// always be false (as the occurence itself doesn't recur.
cloneRec.set('recurring', false);
// For occurences, the 'basedate' is part of the unique id
cloneRec.addIdProp('basedate');
// set delegate properties if needed
if(!cloneRec.userIsStoreOwner()) {
var storeRecord = container.getHierarchyStore().getById(cloneRec.get('store_entryid'));
if(storeRecord) {
cloneRec.setDelegatorInfo(storeRecord, true);
}
}
return cloneRec;
}
return this;
},
/**
* Function is used to convert an occurence record to a series record.
* Series and occurence records has some silghtly differences in properties so we need to reset some
* properties which are only valid for occurence record not for series record.
* Also we need to remove basedate property from {@link #idProperties} so record will be correctly identified
* as a series record.
* @return {Zarafa.core.data.IPMRecord} record that can be used to open whole series.
*/
convertToSeriesRecord : function()
{
// only convert it when its a occurrence record
if(this.hasIdProp('basedate') === true) {
var cloneRec = this.copy();
// Series always have the 'recurring' property to true
// (occurences themselves have this property set to false
cloneRec.set('recurring', true);
// The 'basedate' is not used in series
cloneRec.set('basedate', '');
// A series cannot be an exception
cloneRec.set('exception', false);
// Remove the 'basedate' from the unique id
cloneRec.removeIdProp('basedate');
return cloneRec;
}
return this;
},
/**
* Function is used to convert 'recurrence_weekdays' {@link Zarafa.common.recurrence.data.RecurrenceSubtype subtype}
* property into a comma separated list which contains selected week days. To display weekday information
* as a part of recurring pattern.
* @return {String} A string containing selected week days separated by comma.
*/
prepareWeekDaysString : function()
{
var weekDaysObject = new Ext.util.MixedCollection();
var weekStart = container.getSettingsModel().get('zarafa/v1/main/week_start');
var checkedWeekDays = [];
// Dynamically prepare an object which contains mapping between day name and its value according to the configured week start setting.
for (var i = 0; i < Date.dayNames.length; i++) {
weekDaysObject.add({
dayName: Date.dayNames[(weekStart + i) % 7],
dayValue : Math.pow(2, (weekStart + i) % 7)
});
}
// Get the property value from record to prepare array of selected week days.
var weekdays = this.get('recurrence_weekdays');
weekDaysObject.each(function(dayObject) {
if(!!(dayObject.dayValue & weekdays)){
checkedWeekDays.push(dayObject.dayName);
}
});
// Check if there is more than one days are selected, to append 'and' word before last week day
if(checkedWeekDays.length > 1) {
var lastWeekDayIndex = checkedWeekDays.length - 1;
checkedWeekDays[lastWeekDayIndex] = _('and ') + checkedWeekDays[lastWeekDayIndex];
}
return (checkedWeekDays.length === 2) ? checkedWeekDays.join(" ") : checkedWeekDays.join(", ");
}
});
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.Appointment', Zarafa.calendar.AppointmentRecord);
Zarafa.core.data.RecordFactory.setBaseClassToMessageClass('IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}', Zarafa.calendar.AppointmentRecord);