zimbra-soap-api 0.0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.irbrc +6 -0
  4. data/Gemfile +6 -0
  5. data/README +21 -0
  6. data/Rakefile +1 -0
  7. data/lib/zimbra.rb +94 -0
  8. data/lib/zimbra/account.rb +94 -0
  9. data/lib/zimbra/acl.rb +63 -0
  10. data/lib/zimbra/alias.rb +4 -0
  11. data/lib/zimbra/appointment.rb +274 -0
  12. data/lib/zimbra/appointment/alarm.rb +101 -0
  13. data/lib/zimbra/appointment/attendee.rb +97 -0
  14. data/lib/zimbra/appointment/invite.rb +360 -0
  15. data/lib/zimbra/appointment/recur_exception.rb +83 -0
  16. data/lib/zimbra/appointment/recur_rule.rb +184 -0
  17. data/lib/zimbra/appointment/reply.rb +91 -0
  18. data/lib/zimbra/auth.rb +46 -0
  19. data/lib/zimbra/base.rb +217 -0
  20. data/lib/zimbra/calendar.rb +27 -0
  21. data/lib/zimbra/common_elements.rb +51 -0
  22. data/lib/zimbra/cos.rb +124 -0
  23. data/lib/zimbra/delegate_auth_token.rb +49 -0
  24. data/lib/zimbra/directory.rb +175 -0
  25. data/lib/zimbra/distribution_list.rb +147 -0
  26. data/lib/zimbra/domain.rb +63 -0
  27. data/lib/zimbra/ext/handsoap_curb_driver.rb +21 -0
  28. data/lib/zimbra/ext/hash.rb +72 -0
  29. data/lib/zimbra/ext/string.rb +10 -0
  30. data/lib/zimbra/extra/date_helpers.rb +111 -0
  31. data/lib/zimbra/folder.rb +100 -0
  32. data/lib/zimbra/handsoap_account_service.rb +44 -0
  33. data/lib/zimbra/handsoap_service.rb +75 -0
  34. data/lib/zimbra/version.rb +3 -0
  35. data/spec/fixtures/xml_api_requests/appointments/create.xml +84 -0
  36. data/spec/fixtures/xml_api_responses/alarms/15_minutes_before.xml +26 -0
  37. data/spec/fixtures/xml_api_responses/alarms/using_all_intervals.xml +26 -0
  38. data/spec/fixtures/xml_api_responses/appointments/appointment_response_1.xml +40 -0
  39. data/spec/fixtures/xml_api_responses/attendees/one_attendee_and_one_reply.xml +27 -0
  40. data/spec/fixtures/xml_api_responses/attendees/three_attendees_response_1.xml +30 -0
  41. data/spec/fixtures/xml_api_responses/multiple_invites/recurring_with_exceptions.xml +109 -0
  42. data/spec/fixtures/xml_api_responses/recur_rules/day_27_of_every_2_months.xml +27 -0
  43. data/spec/fixtures/xml_api_responses/recur_rules/every_2_days.xml +26 -0
  44. data/spec/fixtures/xml_api_responses/recur_rules/every_3_weeks_on_tuesday_and_friday.xml +30 -0
  45. data/spec/fixtures/xml_api_responses/recur_rules/every_day_50_instances.xml +27 -0
  46. data/spec/fixtures/xml_api_responses/recur_rules/every_monday_wednesday_friday.xml +31 -0
  47. data/spec/fixtures/xml_api_responses/recur_rules/every_tuesday.xml +29 -0
  48. data/spec/fixtures/xml_api_responses/recur_rules/every_weekday_with_end_date.xml +34 -0
  49. data/spec/fixtures/xml_api_responses/recur_rules/every_year_on_february_2.xml +28 -0
  50. data/spec/fixtures/xml_api_responses/recur_rules/first_day_of_every_month.xml +36 -0
  51. data/spec/fixtures/xml_api_responses/recur_rules/first_monday_of_every_february.xml +31 -0
  52. data/spec/fixtures/xml_api_responses/recur_rules/first_weekend_day_of_every_month.xml +31 -0
  53. data/spec/fixtures/xml_api_responses/recur_rules/last_day_of_every_month.xml +36 -0
  54. data/spec/fixtures/xml_api_responses/recur_rules/second_day_of_every_2_months.xml +36 -0
  55. data/spec/fixtures/xml_api_responses/recur_rules/second_wednesday_of_every_month.xml +29 -0
  56. data/spec/fixtures/xml_api_responses/recur_rules/weekly_with_an_exception.xml +44 -0
  57. data/spec/spec_helper.rb +32 -0
  58. data/spec/zimbra/acl_spec.rb +11 -0
  59. data/spec/zimbra/appointment/alarm_spec.rb +33 -0
  60. data/spec/zimbra/appointment/invite_spec.rb +62 -0
  61. data/spec/zimbra/appointment/recur_rule_spec.rb +307 -0
  62. data/spec/zimbra/appointment_spec.rb +175 -0
  63. data/spec/zimbra/common_elements_spec.rb +33 -0
  64. data/spec/zimbra/distribution_list_spec.rb +54 -0
  65. data/zimbra.gemspec +28 -0
  66. metadata +197 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e2c290f23c8fc11e17f8246a10d6272a38daedc3
4
+ data.tar.gz: cdc7b8ed6e55a96aeb8f7b0d83164ba014742c4b
5
+ SHA512:
6
+ metadata.gz: f180678a46a734e9fdf2d662d797a3f8a063be93519da28302c601527463cb35175f5495c6546092db486ada7928ec344638465d0f5923dd2b9a39f2072e5a34
7
+ data.tar.gz: 60eb433cb481fb18414316d3c0d00bef8643abaab3c2559b31aeb4e51e54e8fc1d95690a85b076464f5cd578c605ea8a1ac67ac12f4e5cccbb3f8769407ce5bf
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.irbrc ADDED
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+ require 'zimbra.rb'
3
+
4
+ puts "* Loaded Zimbra Gem"
5
+
6
+
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ end
data/README ADDED
@@ -0,0 +1,21 @@
1
+ EXAMPLE USAGE
2
+ =============
3
+
4
+ Zimbra.admin_api_url = 'https://mail.server.test:7071/service/admin/soap'
5
+ Zimbra.account_api_url = 'https://mail.server.test/service/soap'
6
+ Zimbra.login('admin@example.com','secret')
7
+
8
+ d = Zimbra::Domain.create('luser.com')
9
+
10
+ dl = Zimbra::DistributionList.create('info@luser.com')
11
+ dl.admin_group = true
12
+ dl.save
13
+
14
+ Zimbra.account_login('user@example.com')
15
+
16
+ calendar = Zimbra::Calendar.all.first
17
+ appointment = calendar.appointments.first
18
+
19
+ # Get Account info
20
+ account = Zimbra::Account.find_by_name("pbruna@example.com")
21
+ account.get_attributes ["displayName", "sn", "zimbraMailAlias"]
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,94 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__)))
2
+ require 'zimbra/handsoap_service'
3
+ require 'zimbra/handsoap_account_service'
4
+ require 'zimbra/base'
5
+ require 'zimbra/auth'
6
+ require 'zimbra/cos'
7
+ require 'zimbra/domain'
8
+ require 'zimbra/distribution_list'
9
+ require 'zimbra/account'
10
+ require 'zimbra/acl'
11
+ require 'zimbra/common_elements'
12
+ require 'zimbra/delegate_auth_token'
13
+ require 'zimbra/folder'
14
+ require 'zimbra/calendar'
15
+ require 'zimbra/appointment'
16
+ require 'zimbra/directory'
17
+ require 'zimbra/alias'
18
+ require 'zimbra/ext/hash'
19
+ require 'zimbra/ext/string'
20
+ require 'zimbra/ext/handsoap_curb_driver'
21
+ require 'zimbra/extra/date_helpers'
22
+
23
+ # Manages a Zimbra SOAP session. Offers ability to set the endpoint URL, log in, and enable debugging.
24
+ module Zimbra
25
+ class << self
26
+
27
+ # The URL that will be used to contact the Zimbra SOAP service
28
+ def admin_api_url
29
+ @@admin_api_url
30
+ end
31
+ # Sets the URL of the Zimbra SOAP service
32
+ def admin_api_url=(url)
33
+ @@admin_api_url = url
34
+ end
35
+
36
+ def account_api_url
37
+ @@account_api_url
38
+ end
39
+
40
+ def account_api_url=(url)
41
+ @@account_api_url = url
42
+ end
43
+
44
+ # Turn debugging on/off. Outputs full SOAP conversations to stdout.
45
+ # Zimbra.debug = true
46
+ # Zimbra.debug = false
47
+ def debug=(val)
48
+ Handsoap::Service.logger = (val ? $stdout : nil)
49
+ @@debug = val
50
+ end
51
+
52
+ # Whether debugging is enabled
53
+ def debug
54
+ @@debug ||= false
55
+ end
56
+
57
+ # Checking if we can update the token
58
+ def auth_token=(token)
59
+ @@auth_token = token
60
+ end
61
+
62
+ # Authorization token - obtained after successful login
63
+ def auth_token
64
+ @@auth_token
65
+ end
66
+
67
+ def session_lifetime
68
+ @@session_lifetime
69
+ end
70
+
71
+ def account_auth_token
72
+ @@account_auth_token
73
+ end
74
+
75
+ # Log into the zimbra SOAP service. This is required before any other action is performed
76
+ # If a login has already been performed, another login will not be attempted
77
+ def login(username, password)
78
+ return @@auth_token if defined?(@@auth_token) && @@auth_token
79
+ reset_login(username, password)
80
+ end
81
+
82
+ # re-log into the zimbra SOAP service
83
+ def reset_login(username, password)
84
+ @@auth_token, @@session_lifetime = Auth.login(username, password)
85
+ end
86
+
87
+ def account_login(username)
88
+ delegate_auth_token = DelegateAuthToken.for_account_name(username)
89
+ return false unless delegate_auth_token
90
+ @@account_auth_token = delegate_auth_token.token
91
+ true
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,94 @@
1
+ module Zimbra
2
+ class Account < Zimbra::Base
3
+ class << self
4
+
5
+ def create(name, password, attributes = {})
6
+ AccountService.create(name, password, attributes)
7
+ end
8
+
9
+ def acl_name
10
+ 'usr'
11
+ end
12
+
13
+ end
14
+
15
+ attr_accessor :cos_id
16
+
17
+ def initialize(id, name, zimbra_attrs = {}, node = nil)
18
+ super
19
+ self.cos_id = zimbra_attrs['zimbraCOSId']
20
+ self.delegated_admin = zimbra_attrs['zimbraIsDelegatedAdminAccount']
21
+ end
22
+
23
+ def delegated_admin=(val)
24
+ @delegated_admin = Zimbra::Boolean.read(val)
25
+ end
26
+
27
+ def delegated_admin?
28
+ @delegated_admin
29
+ end
30
+
31
+ def save
32
+ AccountService.modify(self)
33
+ end
34
+
35
+ def set_password(new_password)
36
+ AccountService.set_password(id, new_password)
37
+ end
38
+
39
+ def add_alias(alias_name)
40
+ AccountService.add_alias(self, alias_name)
41
+ end
42
+ end
43
+
44
+ # Doc Placeholder
45
+ class AccountService < HandsoapService
46
+ def create(name, password, attributes)
47
+ xml = invoke("n2:CreateAccountRequest") do |message|
48
+ Builder.create(message, name, password, attributes)
49
+ end
50
+ class_name = Zimbra::Account.class_name
51
+ Zimbra::BaseService::Parser.response(class_name, xml/"//n2:account")
52
+ end
53
+
54
+ def set_password(id, new_password)
55
+ xml = invoke('n2:SetPasswordRequest') do |message|
56
+ Builder.set_password(message, id, new_password)
57
+ end
58
+ true
59
+ end
60
+
61
+ def add_alias(account,alias_name)
62
+ xml = invoke('n2:AddAccountAliasRequest') do |message|
63
+ Builder.add_alias(message,account.id,alias_name)
64
+ end
65
+ end
66
+
67
+ # Doc Placeholder
68
+ class Builder
69
+ class << self
70
+ def create(message, name, password, attributes)
71
+ message.add 'name', name
72
+ message.add 'password', password
73
+ attributes.each do |k,v|
74
+ A.inject(message, k, v)
75
+ end
76
+ end
77
+
78
+ def set_password(message, id, new_password)
79
+ message.set_attr 'id', id
80
+ message.set_attr 'newPassword', new_password
81
+ end
82
+
83
+ def add_alias(message,id,alias_name)
84
+ message.add 'id', id
85
+ message.add 'alias', alias_name
86
+ end
87
+ end
88
+ end
89
+ class Parser
90
+ class << self
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,63 @@
1
+ module Zimbra
2
+ class ACL
3
+ class TargetObjectNotFound < StandardError; end
4
+
5
+ TARGET_CLASSES = [Zimbra::Domain, Zimbra::DistributionList, Zimbra::Cos, Zimbra::Account]
6
+ TARGET_MAPPINGS = TARGET_CLASSES.inject({}) do |hsh, klass|
7
+ hsh[klass.acl_name] = klass
8
+ hsh[klass] = klass.acl_name
9
+ hsh
10
+ end
11
+
12
+ class << self
13
+ def delete_all(xmldoc)
14
+ A.inject(xmldoc, 'zimbraACE', '', 'c' => '1')
15
+ end
16
+
17
+ def read(node)
18
+ list = A.read(node, 'zimbraACE')
19
+ return nil if list.nil?
20
+ list = [list] unless list.respond_to?(:map)
21
+ acls = list.map do |ace|
22
+ from_s(ace)
23
+ end
24
+ end
25
+
26
+ def from_zimbra(node)
27
+ from_s(node.to_s)
28
+ end
29
+
30
+ def from_s(value)
31
+ grantee_id, grantee_name, name = value.split(' ')
32
+ grantee_class = TARGET_MAPPINGS[grantee_name]
33
+ return "Target object not found for acl #{value}" if target_class.nil?
34
+ new(grantee_id: grantee_id, grantee_class: grantee_class, name: name)
35
+ end
36
+ end
37
+
38
+ attr_accessor :grantee_id, :grantee_class, :name, :grantee_name
39
+
40
+ def initialize(options = {})
41
+ if options[:grantee]
42
+ self.grantee_id = options[:grantee].id
43
+ self.grantee_class = options[:grantee].class
44
+ self.grantee_name = options[:grantee].grantee_name unless options[:grantee].grantee_name.nil?
45
+ else
46
+ self.grantee_id = options[:grantee_id]
47
+ self.grantee_class = options[:grantee_class]
48
+ self.grantee_name = options[:grantee_name] unless options[:grantee_name].nil?
49
+ end
50
+ self.name = options[:name]
51
+ end
52
+
53
+ def to_zimbra_acl_value
54
+ id = grantee_id
55
+ type = grantee_class.acl_name
56
+ "#{id} #{type} #{name}"
57
+ end
58
+
59
+ def apply(xmldoc)
60
+ A.inject(xmldoc, 'zimbraACE', to_zimbra_acl_value, 'c' => '1')
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,4 @@
1
+ module Zimbra
2
+ class Alias
3
+ end
4
+ end
@@ -0,0 +1,274 @@
1
+ # zmsoap -z -m mail03@greenviewdata.com SearchRequest @types="appointment" @query="inid:10"
2
+ # http://files.zimbra.com/docs/soap_api/8.0.4/soap-docs-804/api-reference/zimbraMail/Search.html
3
+ # GetRecurRequest
4
+
5
+ module Zimbra
6
+ class Appointment
7
+ autoload :RecurRule, 'zimbra/appointment/recur_rule'
8
+ autoload :Alarm, 'zimbra/appointment/alarm'
9
+ autoload :Attendee, 'zimbra/appointment/attendee'
10
+ autoload :Reply, 'zimbra/appointment/reply'
11
+ autoload :Invite, 'zimbra/appointment/invite'
12
+ autoload :RecurException, 'zimbra/appointment/recur_exception'
13
+
14
+ class << self
15
+ def find_all_by_calendar_id(calendar_id)
16
+ AppointmentService.find_all_by_calendar_id(calendar_id).collect { |attrs| new_from_zimbra_attributes(attrs.merge(:loaded_from_search => true)) }
17
+ end
18
+
19
+ def find_all_by_calendar_id_since(calendar_id, since_date)
20
+ AppointmentService.find_all_by_calendar_id_since(calendar_id, since_date).collect { |attrs| new_from_zimbra_attributes(attrs.merge(:loaded_from_search => true)) }
21
+ end
22
+
23
+ def find(appointment_id)
24
+ attrs = AppointmentService.find(appointment_id)
25
+ return nil unless attrs
26
+ new_from_zimbra_attributes(attrs)
27
+ end
28
+
29
+ def new_from_zimbra_attributes(zimbra_attributes)
30
+ new(parse_zimbra_attributes(zimbra_attributes))
31
+ end
32
+
33
+ def parse_zimbra_attributes(zimbra_attributes)
34
+ zimbra_attributes = Zimbra::Hash.symbolize_keys(zimbra_attributes.dup, true)
35
+
36
+ return {} unless zimbra_attributes.has_key?(:appt) && zimbra_attributes[:appt].has_key?(:attributes)
37
+
38
+ {
39
+ :id => zimbra_attributes[:appt][:attributes][:id],
40
+ :uid => zimbra_attributes[:appt][:attributes][:uid],
41
+ :revision => zimbra_attributes[:appt][:attributes][:rev],
42
+ :calendar_id => zimbra_attributes[:appt][:attributes][:l],
43
+ :size => zimbra_attributes[:appt][:attributes][:s],
44
+ :replies => zimbra_attributes[:appt][:replies],
45
+ :invites_zimbra_attributes => zimbra_attributes[:appt][:inv],
46
+ :date => zimbra_attributes[:appt][:attributes][:d],
47
+ :loaded_from_search => zimbra_attributes[:loaded_from_search]
48
+ }
49
+ end
50
+ end
51
+
52
+ ATTRS = [
53
+ :id, :uid, :date, :revision, :size, :calendar_id,
54
+ :replies, :invites, :invites_zimbra_attributes, :invites_attributes
55
+ ] unless const_defined?(:ATTRS)
56
+
57
+ attr_accessor *ATTRS
58
+ attr_reader :loaded_from_search
59
+
60
+ def initialize(args = {})
61
+ self.attributes = args
62
+ @loaded_from_search = args[:loaded_from_search] || false
63
+ end
64
+
65
+ def attributes=(args = {})
66
+ ATTRS.each do |attr_name|
67
+ self.send(:"#{attr_name}=", args[attr_name]) if args.has_key?(attr_name)
68
+ end
69
+ end
70
+
71
+ def reload
72
+ raw_attributes = AppointmentService.find(id)
73
+ self.attributes = Zimbra::Appointment.parse_zimbra_attributes(raw_attributes)
74
+ @loaded_from_search = false
75
+ end
76
+
77
+ def replies
78
+ reload if loaded_from_search
79
+ @replies
80
+ end
81
+
82
+ def replies=(replies_attributes)
83
+ return @replies = [] unless replies_attributes
84
+
85
+ replies_attributes = replies_attributes[:reply].is_a?(Array) ? replies_attributes[:reply] : [ replies_attributes[:reply] ]
86
+ @replies = replies_attributes.collect { |attrs| Zimbra::Appointment::Reply.new_from_zimbra_attributes(attrs[:attributes]) }
87
+ end
88
+
89
+ def invites
90
+ reload if loaded_from_search
91
+ @invites
92
+ end
93
+
94
+ def invites_attributes=(attributes)
95
+ return @invites = nil unless attributes
96
+
97
+ attributes = attributes.is_a?(Array) ? attributes : [ attributes ]
98
+ @invites = attributes.collect { |attrs| Zimbra::Appointment::Invite.new(attrs.merge( { :appointment => self } )) }
99
+ end
100
+
101
+ def invites_zimbra_attributes=(attributes)
102
+ return @invites = nil unless attributes
103
+
104
+ attributes = attributes.is_a?(Array) ? attributes : [ attributes ]
105
+ @invites = attributes.collect { |attrs| Zimbra::Appointment::Invite.new_from_zimbra_attributes(attrs.merge( { :appointment => self } )) }
106
+ end
107
+
108
+ def date=(val)
109
+ if val.is_a?(Integer)
110
+ @date = parse_date_in_seconds(val)
111
+ else
112
+ @date = val
113
+ end
114
+ end
115
+
116
+ def create_xml(document, invite_id = nil)
117
+ document.add "m" do |mime|
118
+ mime.set_attr "l", calendar_id
119
+
120
+ invites.each do |invite|
121
+ next unless invite_id.nil? || invite_id == invite.id
122
+
123
+ mime.add "inv" do |invite_element|
124
+ invite.create_xml(invite_element)
125
+ end
126
+ end
127
+ end
128
+
129
+ document
130
+ end
131
+
132
+ def destroy
133
+ invites.each do |invite|
134
+ AppointmentService.cancel(self, invite.id)
135
+ end
136
+ end
137
+
138
+ def save
139
+ if new_record?
140
+ response = Zimbra::AppointmentService.create(self)
141
+ invites.first.id = response[:invite_id]
142
+ @id = response[:id]
143
+ else
144
+ invites.each do |invite|
145
+ Zimbra::AppointmentService.update(self, invite.id)
146
+ end
147
+ end
148
+ end
149
+
150
+ def new_record?
151
+ id.nil?
152
+ end
153
+
154
+ def id_with_invite_id
155
+ "#{id}-#{invites.first.id}"
156
+ end
157
+
158
+ def last_instance_time
159
+ instance_times = Zimbra::AppointmentService.find_all_instances_of_an_appointment(self)
160
+ return nil unless instance_times && instance_times.count > 0
161
+ instance_times.max
162
+ end
163
+
164
+ private
165
+
166
+ def parse_date_in_seconds(seconds)
167
+ Time.at(seconds / 1000)
168
+ end
169
+
170
+ end
171
+
172
+ class AppointmentService < HandsoapAccountService
173
+ def find_all_by_calendar_id(calendar_id)
174
+ xml = invoke("n2:SearchRequest") do |message|
175
+ Builder.find_all_with_query(message, "inid:#{calendar_id}")
176
+ end
177
+ Parser.get_search_response(xml)
178
+ end
179
+
180
+ def find_all_instances_of_an_appointment(appointment)
181
+ xml = invoke("n2:SearchRequest") do |message|
182
+ message.set_attr 'query', "date:#{appointment.date.to_i * 1000}"
183
+ message.set_attr 'types', 'appointment'
184
+ message.set_attr 'calExpandInstStart', '1'
185
+ message.set_attr 'calExpandInstEnd', (Time.now + (86400 * 365 * 10)).to_i * 1000
186
+ end
187
+ response_hash = Zimbra::Hash.from_xml(xml.document.to_s)
188
+ response_hash = response_hash[:Envelope][:Body][:SearchResponse]
189
+ appointments = response_hash[:appt].is_a?(Array) ? response_hash[:appt] : [response_hash[:appt]]
190
+ appt_hash = appointments.find { |appt| appt[:attributes][:id] == appointment.id }
191
+ instances = appt_hash[:inst].is_a?(Array) ? appt_hash[:inst] : [appt_hash[:inst]]
192
+ instances.collect { |inst| Time.at(inst[:attributes][:s] / 1000) }
193
+ end
194
+
195
+ def find_all_by_calendar_id_since(calendar_id, since_date)
196
+ xml = invoke("n2:SearchRequest") do |message|
197
+ Builder.find_all_with_query(message, "inid:#{calendar_id} AND date:>#{since_date.to_i}")
198
+ end
199
+ Parser.get_search_response(xml)
200
+ end
201
+
202
+ def find(appointment_id)
203
+ xml = invoke("n2:GetAppointmentRequest") do |message|
204
+ Builder.find_by_id(message, appointment_id)
205
+ end
206
+ return nil unless xml
207
+ Parser.appointment_response(xml/"//n2:appt")
208
+ end
209
+
210
+ def create(appointment)
211
+ xml = invoke("n2:CreateAppointmentRequest") do |message|
212
+ Builder.create(message, appointment)
213
+ end
214
+ response_hash = Zimbra::Hash.from_xml(xml.document.to_s)
215
+ id = response_hash[:Envelope][:Body][:CreateAppointmentResponse][:attributes][:apptId] rescue nil
216
+ invite_id = response_hash[:Envelope][:Body][:CreateAppointmentResponse][:attributes][:invId].gsub(/#{id}\-/, '').to_i rescue nil
217
+ { :id => id, :invite_id => invite_id }
218
+ end
219
+
220
+ def update(appointment, invite_id)
221
+ xml = invoke("n2:ModifyAppointmentRequest") do |message|
222
+ Builder.update(message, appointment, invite_id)
223
+ end
224
+ end
225
+
226
+ def cancel(appointment, invite_id)
227
+ xml = invoke("n2:CancelAppointmentRequest") do |message|
228
+ Builder.cancel(message, appointment.id, invite_id)
229
+ end
230
+ end
231
+
232
+ class Builder
233
+ class << self
234
+ def find_all_with_query(message, query)
235
+ message.set_attr 'query', query
236
+ message.set_attr 'types', 'appointment'
237
+ end
238
+
239
+ def find_by_id(message, id)
240
+ message.set_attr 'id', id
241
+ end
242
+
243
+ def create(message, appointment)
244
+ appointment.create_xml(message)
245
+ end
246
+
247
+ def update(message, appointment, invite_id)
248
+ message.set_attr 'id', "#{appointment.id}-#{invite_id}"
249
+ appointment.create_xml(message, invite_id)
250
+ end
251
+
252
+ def cancel(message, appointment_id, invite_id)
253
+ message.set_attr 'id', "#{appointment_id}-#{invite_id}"
254
+ message.set_attr 'comp', 0
255
+ end
256
+ end
257
+ end
258
+
259
+ class Parser
260
+ class << self
261
+ def get_search_response(response)
262
+ (response/"//n2:appt").collect do |node|
263
+ Zimbra::Hash.from_xml(node.to_xml)
264
+ end
265
+ end
266
+
267
+ def appointment_response(node)
268
+ # It's much easier to deal with this as a hash
269
+ Zimbra::Hash.from_xml(node.to_xml)
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end