zimbra 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +4 -0
  2. data/.irbrc +6 -0
  3. data/Gemfile +6 -0
  4. data/README +3 -2
  5. data/Rakefile +1 -0
  6. data/lib/zimbra.rb +31 -6
  7. data/lib/zimbra/appointment.rb +274 -0
  8. data/lib/zimbra/appointment/alarm.rb +101 -0
  9. data/lib/zimbra/appointment/attendee.rb +97 -0
  10. data/lib/zimbra/appointment/invite.rb +360 -0
  11. data/lib/zimbra/appointment/recur_exception.rb +83 -0
  12. data/lib/zimbra/appointment/recur_rule.rb +184 -0
  13. data/lib/zimbra/appointment/reply.rb +91 -0
  14. data/lib/zimbra/calendar.rb +27 -0
  15. data/lib/zimbra/delegate_auth_token.rb +49 -0
  16. data/lib/zimbra/ext/hash.rb +72 -0
  17. data/lib/zimbra/extra/date_helpers.rb +111 -0
  18. data/lib/zimbra/folder.rb +100 -0
  19. data/lib/zimbra/handsoap_account_service.rb +44 -0
  20. data/lib/zimbra/handsoap_service.rb +1 -1
  21. data/lib/zimbra/version.rb +3 -0
  22. data/spec/fixtures/xml_api_requests/appointments/create.xml +84 -0
  23. data/spec/fixtures/xml_api_responses/alarms/15_minutes_before.xml +26 -0
  24. data/spec/fixtures/xml_api_responses/alarms/using_all_intervals.xml +26 -0
  25. data/spec/fixtures/xml_api_responses/appointments/appointment_response_1.xml +40 -0
  26. data/spec/fixtures/xml_api_responses/attendees/one_attendee_and_one_reply.xml +27 -0
  27. data/spec/fixtures/xml_api_responses/attendees/three_attendees_response_1.xml +30 -0
  28. data/spec/fixtures/xml_api_responses/multiple_invites/recurring_with_exceptions.xml +109 -0
  29. data/spec/fixtures/xml_api_responses/recur_rules/day_27_of_every_2_months.xml +27 -0
  30. data/spec/fixtures/xml_api_responses/recur_rules/every_2_days.xml +26 -0
  31. data/spec/fixtures/xml_api_responses/recur_rules/every_3_weeks_on_tuesday_and_friday.xml +30 -0
  32. data/spec/fixtures/xml_api_responses/recur_rules/every_day_50_instances.xml +27 -0
  33. data/spec/fixtures/xml_api_responses/recur_rules/every_monday_wednesday_friday.xml +31 -0
  34. data/spec/fixtures/xml_api_responses/recur_rules/every_tuesday.xml +29 -0
  35. data/spec/fixtures/xml_api_responses/recur_rules/every_weekday_with_end_date.xml +34 -0
  36. data/spec/fixtures/xml_api_responses/recur_rules/every_year_on_february_2.xml +28 -0
  37. data/spec/fixtures/xml_api_responses/recur_rules/first_day_of_every_month.xml +36 -0
  38. data/spec/fixtures/xml_api_responses/recur_rules/first_monday_of_every_february.xml +31 -0
  39. data/spec/fixtures/xml_api_responses/recur_rules/first_weekend_day_of_every_month.xml +31 -0
  40. data/spec/fixtures/xml_api_responses/recur_rules/last_day_of_every_month.xml +36 -0
  41. data/spec/fixtures/xml_api_responses/recur_rules/second_day_of_every_2_months.xml +36 -0
  42. data/spec/fixtures/xml_api_responses/recur_rules/second_wednesday_of_every_month.xml +29 -0
  43. data/spec/fixtures/xml_api_responses/recur_rules/weekly_with_an_exception.xml +44 -0
  44. data/spec/spec_helper.rb +32 -0
  45. data/spec/zimbra/acl_spec.rb +11 -0
  46. data/spec/zimbra/appointment/alarm_spec.rb +33 -0
  47. data/spec/zimbra/appointment/invite_spec.rb +62 -0
  48. data/spec/zimbra/appointment/recur_rule_spec.rb +307 -0
  49. data/spec/zimbra/appointment_spec.rb +175 -0
  50. data/spec/zimbra/common_elements_spec.rb +33 -0
  51. data/spec/zimbra/distribution_list_spec.rb +54 -0
  52. data/zimbra.gemspec +28 -0
  53. metadata +165 -68
@@ -0,0 +1,97 @@
1
+ module Zimbra
2
+ class Appointment
3
+ class Attendee
4
+ ATTRS = [
5
+ :email_address, :friendly_name, :rsvp, :role, :participation_status
6
+ ] unless const_defined?(:ATTRS)
7
+
8
+ attr_accessor *ATTRS
9
+
10
+ class << self
11
+ def new_from_zimbra_attributes(zimbra_attributes)
12
+ new(parse_zimbra_attributes(zimbra_attributes))
13
+ end
14
+
15
+ def parse_zimbra_attributes(zimbra_attributes)
16
+ zimbra_attributes = Zimbra::Hash.symbolize_keys(zimbra_attributes.dup, true)
17
+
18
+ {
19
+ :email_address => zimbra_attributes[:a],
20
+ :friendly_name => zimbra_attributes[:d],
21
+ :rsvp => zimbra_attributes[:rsvp],
22
+ :role => zimbra_attributes[:role],
23
+ :participation_status => zimbra_attributes[:ptst]
24
+ }
25
+ end
26
+ end
27
+
28
+ def initialize(args = {})
29
+ self.attributes = args
30
+ end
31
+
32
+ # take attributes by the xml name or our more descriptive name
33
+ def attributes=(args = {})
34
+ ATTRS.each do |attr_name|
35
+ if args.has_key?(attr_name)
36
+ self.send(:"#{attr_name}=", args[attr_name])
37
+ elsif args.has_key?(attr_name.to_s)
38
+ self.send(:"#{attr_name}=", args[attr_name.to_s])
39
+ end
40
+ end
41
+ end
42
+
43
+ def participation_status=(val)
44
+ @participation_status = parse_participation_status(val)
45
+ end
46
+
47
+ def participation_status_to_zimbra
48
+ possible_participation_status_values.find { |k, v| v == participation_status }.first rescue participiation_status
49
+ end
50
+
51
+ def to_hash(options = {})
52
+ hash = {
53
+ :email_address => email_address,
54
+ :friendly_name => friendly_name,
55
+ :rsvp => rsvp,
56
+ :role => role,
57
+ :participation_status => participation_status
58
+ }
59
+ hash.reject! { |key, value| options[:except].include?(key.to_sym) || options[:except].include?(key.to_s) } if options[:except]
60
+ hash.reject! { |key, value| !options[:only].include?(key.to_sym) && !options[:only].include?(key.to_s) } if options[:only]
61
+ hash
62
+ end
63
+
64
+ def create_xml(document)
65
+ document.add "at" do |at_element|
66
+ at_element.set_attr "a", email_address
67
+ at_element.set_attr "d", friendly_name
68
+ at_element.set_attr "ptst", participation_status_to_zimbra
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def possible_participation_status_values
75
+ @possible_participation_status_values ||= {
76
+ 'NE' => :needs_action,
77
+ 'AC' => :accept,
78
+ 'TE' => :tentative,
79
+ 'DE' => :declined,
80
+ 'DG' => :delegated,
81
+ 'CO' => :completed,
82
+ 'IN' => :in_process,
83
+ 'WE' => :waiting,
84
+ 'DF' => :deferred,
85
+ 'New' => :needs_action,
86
+ 'Accepted' => :accept,
87
+ 'Declined' => :declined
88
+ }
89
+ end
90
+
91
+ def parse_participation_status(status)
92
+ possible_participation_status_values[status] || status
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,360 @@
1
+ module Zimbra
2
+ class Appointment
3
+ class Invite
4
+ ATTRS = [
5
+ :appointment,
6
+ :id, :recurrence_id, :sequence_number,
7
+
8
+ :start_date_time, :end_date_time, :date,
9
+ :name, :fragment, :description, :alarm, :alarm_zimbra_attributes, :alarm_attributes, :recurrence_rule, :recurrence_rule_zimbra_attributes, :recurrence_rule_attributes,
10
+ :attendees, :attendees_attributes, :attendees_zimbra_attributes,
11
+ :organizer_email_address, :is_organizer, :computed_free_busy_status,
12
+ :free_busy_setting, :invite_status, :all_day, :visibility, :location,
13
+ :transparency, :replies, :never_sent,
14
+
15
+ :comment, :recurrence_id_utc, :exception
16
+ ] unless const_defined?(:ATTRS)
17
+
18
+ attr_accessor *ATTRS
19
+
20
+ class << self
21
+ def new_from_zimbra_attributes(zimbra_attributes)
22
+ new(parse_zimbra_attributes(zimbra_attributes))
23
+ end
24
+
25
+ def parse_zimbra_attributes(zimbra_attributes)
26
+ zimbra_attributes = Zimbra::Hash.symbolize_keys(zimbra_attributes.dup, true)
27
+
28
+ attrs = {}
29
+
30
+ attrs[:appointment] = zimbra_attributes[:appointment] if zimbra_attributes.has_key?(:appointment)
31
+
32
+ if zimbra_attributes.has_key?(:attributes)
33
+ attrs.merge!({
34
+ :id => zimbra_attributes[:attributes][:id],
35
+ :recurrence_id => zimbra_attributes[:attributes][:recurId],
36
+ :sequence_number => zimbra_attributes[:attributes][:seq],
37
+ })
38
+ end
39
+
40
+ zimbra_attributes = zimbra_attributes[:comp]
41
+ return attrs unless zimbra_attributes
42
+
43
+ attrs.merge!({
44
+ :fragment => zimbra_attributes[:fr],
45
+ :description => zimbra_attributes[:desc],
46
+ :comment => zimbra_attributes[:comment],
47
+ :alarm_zimbra_attributes => zimbra_attributes[:alarm],
48
+ :recurrence_rule_zimbra_attributes => zimbra_attributes[:recur],
49
+ :attendees_zimbra_attributes => zimbra_attributes[:at],
50
+ :never_sent => zimbra_attributes[:neverSent]
51
+ })
52
+
53
+ if zimbra_attributes[:s]
54
+ attrs[:start_date_time] = zimbra_attributes[:s][:attributes][:u]
55
+ else
56
+
57
+ end
58
+
59
+ if zimbra_attributes[:e]
60
+ attrs[:end_date_time] = zimbra_attributes[:e][:attributes][:u]
61
+ end
62
+
63
+ if zimbra_attributes[:exceptId] && zimbra_attributes[:exceptId][:attributes]
64
+ attrs[:exception] = zimbra_attributes[:exceptId][:attributes]
65
+ end
66
+
67
+ attrs[:organizer_email_address] = zimbra_attributes[:or][:attributes][:a] rescue nil
68
+
69
+ return attrs unless zimbra_attributes.has_key?(:attributes)
70
+
71
+ attrs.merge!({
72
+ :name => zimbra_attributes[:attributes][:name],
73
+ :computed_free_busy_status => zimbra_attributes[:attributes][:fba],
74
+ :free_busy_setting => zimbra_attributes[:attributes][:fb],
75
+ :date => zimbra_attributes[:attributes][:d],
76
+ :invite_status => zimbra_attributes[:attributes][:status],
77
+ :all_day => zimbra_attributes[:attributes][:allDay],
78
+ :visibility => zimbra_attributes[:attributes][:class],
79
+ :location => zimbra_attributes[:attributes][:loc],
80
+ :date => zimbra_attributes[:attributes][:d],
81
+ :transparency => zimbra_attributes[:attributes][:transp],
82
+ :is_organizer => zimbra_attributes[:attributes][:isOrg],
83
+ :recurrence_id_utc => zimbra_attributes[:attributes][:ridZ]
84
+ })
85
+
86
+ attrs
87
+ end
88
+ end
89
+
90
+ def initialize(args = {})
91
+ self.attributes = args
92
+ end
93
+
94
+ # take attributes by the xml name or our more descriptive name
95
+ def attributes=(args = {})
96
+ ATTRS.each do |attr_name|
97
+ if args.has_key?(attr_name)
98
+ self.send(:"#{attr_name}=", args[attr_name])
99
+ elsif args.has_key?(attr_name.to_s)
100
+ self.send(:"#{attr_name}=", args[attr_name.to_s])
101
+ end
102
+ end
103
+ end
104
+
105
+ def save
106
+ return false unless appointment
107
+
108
+ if appointment.new_record?
109
+ appointment.save
110
+ else
111
+ Zimbra::AppointmentService.update(appointment, id)
112
+ end
113
+ end
114
+
115
+ def cancel
116
+ return false unless appointment
117
+ return false if appointment.new_record?
118
+
119
+ Zimbra::AppointmentService.cancel(appointment, id)
120
+ end
121
+
122
+ def exception=(exception_attributes)
123
+ return @exception = nil unless exception_attributes
124
+
125
+ @exception = Zimbra::Appointment::RecurException.new_from_zimbra_attributes(exception_attributes)
126
+ end
127
+
128
+ def recurrence_rule_attributes=(attributes)
129
+ @recurrence_rule = attributes ? Zimbra::Appointment::RecurRule.new(attributes) : nil
130
+ end
131
+
132
+ def recurrence_rule_zimbra_attributes=(attributes)
133
+ @recurrence_rule = attributes ? Zimbra::Appointment::RecurRule.new_from_zimbra_attributes(attributes) : nil
134
+ end
135
+
136
+ def attendees_zimbra_attributes=(attributes)
137
+ return @attendees = [] unless attributes
138
+
139
+ attributes = attributes.is_a?(Array) ? attributes : [attributes]
140
+ @attendees = attributes.collect { |attrs| Zimbra::Appointment::Attendee.new_from_zimbra_attributes(attrs[:attributes]) }
141
+ end
142
+
143
+ def attendees_attributes=(attributes)
144
+ return @attendees = [] unless attributes
145
+
146
+ attributes = attributes.is_a?(Array) ? attributes : [attributes]
147
+ @attendees = attributes.collect { |attrs| Zimbra::Appointment::Attendee.new(attrs) }
148
+ end
149
+
150
+ def alarm_attributes=(attributes)
151
+ @alarm = attributes ? Zimbra::Appointment::Alarm.new(attributes.merge(:appointment_invite => self)) : nil
152
+ end
153
+
154
+ def alarm_zimbra_attributes=(attributes)
155
+ @alarm = attributes ? Zimbra::Appointment::Alarm.new_from_zimbra_attributes(attributes.merge(:appointment_invite => self)) : nil
156
+ end
157
+
158
+ def computed_free_busy_status=(val)
159
+ @computed_free_busy_status = parse_free_busy_status(val)
160
+ end
161
+
162
+ def free_busy_setting_to_zimbra
163
+ possible_free_busy_status_values.find { |k ,v| v == free_busy_setting }.first rescue free_busy_setting
164
+ end
165
+
166
+ def free_busy_setting=(val)
167
+ @free_busy_setting = parse_free_busy_status(val)
168
+ end
169
+
170
+ def invite_status=(val)
171
+ @invite_status = parse_invite_status(val)
172
+ end
173
+
174
+ def invite_status_to_zimbra
175
+ possible_invite_status_values.find { |k, v| v == invite_status }.first rescue invite_status
176
+ end
177
+
178
+ def visibility=(val)
179
+ @visibility = parse_visibility_status(val)
180
+ end
181
+
182
+ def visibility_to_zimbra
183
+ possible_visibility_values.find { |k, v| v == visibility }.first rescue visibility
184
+ end
185
+
186
+ def transparency_to_zimbra
187
+ possible_transparency_status_values.find { |k, v| v == transparency }.first rescue transparency
188
+ end
189
+
190
+ def transparency=(val)
191
+ @transparency = parse_transparency_status(val)
192
+ end
193
+
194
+ def all_day=(val)
195
+ @all_day = parse_all_day(val)
196
+ end
197
+
198
+ def start_date_time=(val)
199
+ @start_date_time = val.is_a?(Integer) ? parse_date_in_seconds(val) : val
200
+ end
201
+
202
+ def end_date_time=(val)
203
+ @end_date_time = val.is_a?(Integer) ? parse_date_in_seconds(val) : val
204
+ end
205
+
206
+ def date=(val)
207
+ @date = val.is_a?(Integer) ? parse_date_in_seconds(val) : val
208
+ end
209
+
210
+ def create_xml(document)
211
+ document.set_attr "id", id if id
212
+ document.set_attr "status", invite_status_to_zimbra if invite_status
213
+ document.set_attr "isOrg", is_organizer ? 1 : 0
214
+ #document.set_attr "d", date.to_i * 1000 if date
215
+
216
+ document.add "comp" do |comp|
217
+ comp.set_attr "loc", location if location
218
+ comp.set_attr "allDay", all_day ? 1 : 0
219
+ comp.set_attr "class", visibility_to_zimbra if visibility
220
+ comp.set_attr "fb", free_busy_setting_to_zimbra if free_busy_setting
221
+ comp.set_attr "isOrg", is_organizer ? 1 : 0
222
+ comp.set_attr "name", name
223
+ comp.set_attr "transp", transparency_to_zimbra if transparency
224
+ comp.set_attr "d", date.to_i * 1000 if date
225
+
226
+ if is_organizer
227
+ comp.set_attr "neverSent", never_sent ? 1 : 0
228
+ end
229
+
230
+ if description && !description.empty?
231
+ comp.add "desc", description
232
+ end
233
+
234
+ if comment && !comment.empty?
235
+ comp.add "comment", comment
236
+ end
237
+
238
+ comp.add "s" do |s_element|
239
+ s_element.set_attr "u", start_date_time.to_i * 1000
240
+ s_element.set_attr "d", start_date_time.utc.strftime("%Y%m%dT%H%M%SZ")
241
+ end
242
+
243
+ comp.add "e" do |e_element|
244
+ e_element.set_attr "u", end_date_time.to_i * 1000
245
+ e_element.set_attr "d", end_date_time.utc.strftime("%Y%m%dT%H%M%SZ")
246
+ end
247
+
248
+ if alarm
249
+ comp.add "alarm" do |alarm_element|
250
+ alarm_element.set_attr "action", "DISPLAY"
251
+
252
+ alarm.create_xml(alarm_element)
253
+ end
254
+ end
255
+
256
+ if recurrence_rule
257
+ comp.add "recur" do |recur_element|
258
+ recur_element.add "add" do |add_element|
259
+ recurrence_rule.create_xml(add_element)
260
+ end
261
+ end
262
+ end
263
+
264
+ if attendees && attendees.count > 0
265
+ attendees.each do |attendee|
266
+ attendee.create_xml(comp)
267
+ end
268
+ end
269
+
270
+ if organizer_email_address
271
+ comp.add "or" do |or_element|
272
+ or_element.set_attr "a", organizer_email_address
273
+ end
274
+ end
275
+
276
+ if exception
277
+ exception.create_xml(comp)
278
+ end
279
+ end
280
+ #
281
+ # ATTRS = [
282
+ # :recurrence_id, :sequence_number, :replies,
283
+ #
284
+ # :, :recurrence_id_utc,
285
+ end
286
+
287
+ private
288
+
289
+ def parse_date_in_seconds(seconds)
290
+ Time.at(seconds / 1000)
291
+ end
292
+
293
+ def possible_all_day_values
294
+ @possible_all_day_values ||= {
295
+ 0 => false,
296
+ 1 => true
297
+ }
298
+ end
299
+
300
+ def parse_all_day(val)
301
+ return val if val == true || val == false
302
+ possible_all_day_values[val.to_i] || val
303
+ end
304
+
305
+ def possible_free_busy_status_values
306
+ @possible_free_busy_status_values ||= {
307
+ 'F' => :free,
308
+ 'B' => :busy,
309
+ 'T' => :busy_tentative,
310
+ 'U' => :busy_unavailable
311
+ }
312
+ end
313
+
314
+ def parse_free_busy_status(fb_status)
315
+ possible_free_busy_status_values[fb_status] || fb_status
316
+ end
317
+
318
+ def possible_invite_status_values
319
+ @possible_invite_status_values ||= {
320
+ 'TENT' => :tentative,
321
+ 'CONF' => :confirmed,
322
+ 'CANC' => :canceled,
323
+ 'NEED' => :need, # Not sure about this one, it's not documented
324
+ 'COMP' => :completed,
325
+ 'INPR' => :in_progress,
326
+ 'WAITING' => :waiting,
327
+ 'DEFERRED' => :deferred
328
+ }
329
+ end
330
+
331
+ def parse_invite_status(status)
332
+ possible_invite_status_values[status] || status
333
+ end
334
+
335
+ def possible_visibility_values
336
+ @possible_visibility_values ||= {
337
+ 'PUB' => :public,
338
+ 'PRI' => :private,
339
+ 'CON' => :confidential
340
+ }
341
+ end
342
+
343
+ def parse_visibility_status(status)
344
+ possible_visibility_values[status] || status
345
+ end
346
+
347
+ def possible_transparency_status_values
348
+ @possible_transparency_status_values ||= {
349
+ 'O' => :opaque,
350
+ 'T' => :transparent
351
+ }
352
+ end
353
+
354
+ def parse_transparency_status(status)
355
+ possible_transparency_status_values[status] || status
356
+ end
357
+
358
+ end
359
+ end
360
+ end