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,83 @@
1
+ module Zimbra
2
+ class Appointment
3
+ class RecurException
4
+ class << self
5
+ def new_from_zimbra_attributes(zimbra_attributes)
6
+ return nil unless zimbra_attributes
7
+ new(parse_zimbra_attributes(zimbra_attributes))
8
+ end
9
+
10
+ def parse_zimbra_attributes(zimbra_attributes)
11
+ zimbra_attributes = Zimbra::Hash.symbolize_keys(zimbra_attributes.dup, true)
12
+
13
+ {
14
+ :recurrence_id => zimbra_attributes[:d],
15
+ :timezone => zimbra_attributes[:tz],
16
+ :range_type => zimbra_attributes[:rangeType]
17
+ }
18
+ end
19
+ end
20
+
21
+ ATTRS = [
22
+ :recurrence_id,
23
+ :timezone,
24
+ :range_type
25
+ ] unless const_defined?(:ATTRS)
26
+
27
+ attr_accessor *ATTRS
28
+
29
+ def range_type=(val)
30
+ @range_type = parse_range_type(val)
31
+ end
32
+
33
+ def initialize(args = {})
34
+ self.attributes = args
35
+ end
36
+
37
+ # take attributes by the xml name or our more descriptive name
38
+ def attributes=(args = {})
39
+ ATTRS.each do |attr_name|
40
+ self.send(:"#{attr_name}=", (args[attr_name] || args[attr_name.to_s]))
41
+ end
42
+ end
43
+
44
+ def to_hash(options = {})
45
+ hash = ATTRS.inject({}) do |attr_hash, attr_name|
46
+ attr_hash[attr_name] = self.send(:"#{attr_name}")
47
+ attr_hash
48
+ end
49
+ hash.reject! { |key, value| options[:except].include?(key.to_sym) || options[:except].include?(key.to_s) } if options[:except]
50
+ hash.reject! { |key, value| !options[:only].include?(key.to_sym) && !options[:only].include?(key.to_s) } if options[:only]
51
+ hash
52
+ end
53
+
54
+ def range_type_to_zimbra
55
+ possible_range_type_values.find { |k, v| v == range_type }.first rescue range_type
56
+ end
57
+
58
+ def create_xml(document)
59
+ document.add "exceptId" do |except_element|
60
+ except_element.set_attr "d", recurrence_id
61
+ except_element.set_attr "tz", timezone
62
+ except_element.set_attr "rangeType", range_type_to_zimbra if range_type
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def possible_range_type_values
69
+ @possible_range_type_values ||= {
70
+ 1 => :none,
71
+ 2 => :this_and_future,
72
+ 3 => :this_and_prior
73
+ }
74
+ end
75
+
76
+ def parse_range_type(val)
77
+ int_val = val.to_int rescue nil
78
+
79
+ possible_range_type_values[int_val] || val
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,184 @@
1
+ module Zimbra
2
+ class Appointment
3
+ class RecurRule
4
+ class << self
5
+ def new_from_zimbra_attributes(zimbra_attributes)
6
+ return nil unless zimbra_attributes
7
+ new(parse_zimbra_attributes(zimbra_attributes))
8
+ end
9
+
10
+ def parse_zimbra_attributes(zimbra_attributes)
11
+ attrs = {}
12
+
13
+ zimbra_attributes = Zimbra::Hash.symbolize_keys(zimbra_attributes.dup, true)
14
+ zimbra_attributes = zimbra_attributes[:add][:rule]
15
+
16
+ attrs[:frequency] = zimbra_attributes[:attributes][:freq] if zimbra_attributes[:attributes]
17
+ attrs[:until_date] = zimbra_attributes[:until][:attributes][:d] if zimbra_attributes[:until]
18
+ attrs[:interval] = zimbra_attributes[:interval][:attributes][:ival] if zimbra_attributes[:interval]
19
+ attrs[:count] = zimbra_attributes[:count][:attributes][:num] if zimbra_attributes[:count]
20
+
21
+ if zimbra_attributes[:bysetpos]
22
+ attrs[:by_set_position] = zimbra_attributes[:bysetpos][:attributes][:poslist]
23
+ attrs[:by_set_position] = [attrs[:by_set_position]] unless attrs[:by_set_position].is_a?(Array)
24
+ end
25
+
26
+ if zimbra_attributes[:byday] && zimbra_attributes[:byday][:wkday] && zimbra_attributes[:byday][:wkday].is_a?(Array)
27
+ attrs[:by_day] = zimbra_attributes[:byday][:wkday].collect do |wkday|
28
+ wkday = Zimbra::Hash.symbolize_keys(wkday, true)
29
+ wkday_hash = { day: wkday[:attributes][:day] }
30
+ wkday_hash[:week_number] = wkday[:attributes][:ordwk] if wkday[:attributes][:ordwk]
31
+ wkday_hash
32
+ end
33
+ elsif zimbra_attributes[:byday] && zimbra_attributes[:byday][:wkday]
34
+ day_hash = { day: zimbra_attributes[:byday][:wkday][:attributes][:day] }
35
+ day_hash[:week_number] = zimbra_attributes[:byday][:wkday][:attributes][:ordwk] if zimbra_attributes[:byday][:wkday][:attributes][:ordwk]
36
+ attrs[:by_day] = [day_hash]
37
+ end
38
+
39
+ if zimbra_attributes[:bymonth]
40
+ attrs[:by_month] = zimbra_attributes[:bymonth][:attributes][:molist]
41
+ attrs[:by_month] = [attrs[:by_month]] unless attrs[:by_month].is_a?(Array)
42
+ end
43
+
44
+ if zimbra_attributes[:bymonthday]
45
+ attrs[:by_month_day] = zimbra_attributes[:bymonthday][:attributes][:modaylist]
46
+ attrs[:by_month_day] = [attrs[:by_month_day]] unless attrs[:by_month_day].is_a?(Array)
47
+ end
48
+
49
+ attrs
50
+ end
51
+ end
52
+
53
+ ATTRS = [
54
+ :frequency,
55
+ :interval,
56
+ :by_day,
57
+ :by_month,
58
+ :by_month_day,
59
+ :count,
60
+ :until_date,
61
+ :by_set_position
62
+ ] unless const_defined?(:ATTRS)
63
+
64
+ attr_accessor *ATTRS
65
+
66
+ def initialize(args = {})
67
+ self.attributes = args
68
+ end
69
+
70
+ # take attributes by the xml name or our more descriptive name
71
+ def attributes=(args = {})
72
+ ATTRS.each do |attr_name|
73
+ if args.has_key?(attr_name)
74
+ self.send(:"#{attr_name}=", args[attr_name])
75
+ elsif args.has_key?(attr_name.to_s)
76
+ self.send(:"#{attr_name}=", args[attr_name.to_s])
77
+ end
78
+ end
79
+ end
80
+
81
+ def frequency=(val)
82
+ frequency = Zimbra::DateHelpers::Frequency.find(val).name
83
+ @frequency = frequency || val
84
+ end
85
+
86
+ def frequency_to_zimbra
87
+ Zimbra::DateHelpers::Frequency.find(frequency).zimbra_name rescue frequency
88
+ end
89
+
90
+ def until_date=(val)
91
+ @until_date = Time.parse(val) rescue val
92
+ end
93
+
94
+ def by_set_position=(val)
95
+ if val == [0]
96
+ @by_set_position = nil
97
+ else
98
+ @by_set_position = val
99
+ end
100
+ end
101
+ def by_day=(val)
102
+ @by_day = if val.is_a?(Array)
103
+ val.collect do |day_specification|
104
+ day_specification[:day] = Zimbra::DateHelpers::WeekDay.find(day_specification[:day]) rescue day_specification[:day]
105
+ day_specification
106
+ end
107
+ else
108
+ val
109
+ end
110
+ end
111
+
112
+ def to_hash(options = {})
113
+ hash = {
114
+ :frequency => frequency ? frequency.to_sym : nil,
115
+ :interval => interval,
116
+ :by_month => by_month,
117
+ :by_month_day => by_month_day,
118
+ :count => count,
119
+ :until_date => until_date,
120
+ :by_set_position => by_set_position
121
+ }
122
+ hash[:by_day] = by_day.collect do |day_specification|
123
+ day_specification[:day] = day_specification[:day].to_sym if day_specification[:day]
124
+ day_specification
125
+ end if by_day
126
+ hash.reject! { |key, value| value.nil? }
127
+ hash.reject! { |key, value| options[:except].include?(key.to_sym) || options[:except].include?(key.to_s) } if options[:except]
128
+ hash.reject! { |key, value| !options[:only].include?(key.to_sym) && !options[:only].include?(key.to_s) } if options[:only]
129
+ hash
130
+ end
131
+
132
+ def create_xml(document)
133
+ document.add "rule" do |rule_element|
134
+ rule_element.set_attr "freq", frequency_to_zimbra
135
+
136
+ if until_date
137
+ rule_element.add "until" do |until_element|
138
+ until_element.set_attr "d", until_date.utc.strftime("%Y%m%dT%H%M%SZ")
139
+ end
140
+ end
141
+
142
+ rule_element.add "interval" do |interval_element|
143
+ interval_element.set_attr "ival", interval
144
+ end
145
+
146
+ if count && count > 0
147
+ rule_element.add "count" do |count_element|
148
+ count_element.set_attr "num", count
149
+ end
150
+ end
151
+
152
+ if by_day && by_day.count > 0
153
+ rule_element.add "byday" do |by_day_element|
154
+ by_day.each do |day_spec|
155
+ by_day_element.add "wkday" do |wkday_element|
156
+ wkday_element.set_attr "day", day_spec[:day].zimbra_name
157
+ wkday_element.set_attr "ordwk", day_spec[:week_number] if day_spec.has_key?(:week_number)
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ if by_month && by_month.count > 0
164
+ rule_element.add "bymonth" do |by_month_element|
165
+ by_month_element.set_attr "molist", by_month.join(',')
166
+ end
167
+ end
168
+
169
+ if by_month_day && by_month_day.count > 0
170
+ rule_element.add "bymonthday" do |by_month_day_element|
171
+ by_month_day_element.set_attr "modaylist", by_month_day.join(',')
172
+ end
173
+ end
174
+
175
+ if by_set_position
176
+ rule_element.add "bysetpos" do |bysetpos_element|
177
+ bysetpos_element.set_attr "poslist", by_set_position.join(',')
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,91 @@
1
+ module Zimbra
2
+ class Appointment
3
+ class Reply
4
+ ATTRS = [
5
+ :sequence_number, :date, :email_address, :participation_status, :sent_by, :recurrence_range_type, :recurrence_id, :timezone, :recurrence_id_utc
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
+ :sequence_number => zimbra_attributes[:seq],
20
+ :date => zimbra_attributes[:d],
21
+ :email_address => zimbra_attributes[:at],
22
+ :participation_status => zimbra_attributes[:ptst],
23
+ :sent_by => zimbra_attributes[:sentBy],
24
+ :recurrence_range_type => zimbra_attributes[:rangeType],
25
+ :recurrence_id => zimbra_attributes[:recurId],
26
+ :timezone => zimbra_attributes[:tz],
27
+ :recurrence_id_utc => zimbra_attributes[:ridZ]
28
+ }
29
+ end
30
+ end
31
+
32
+ def initialize(args = {})
33
+ self.attributes = args
34
+ end
35
+
36
+ # take attributes by the xml name or our more descriptive name
37
+ def attributes=(args = {})
38
+ ATTRS.each do |attr_name|
39
+ if args.has_key?(attr_name)
40
+ self.send(:"#{attr_name}=", args[attr_name])
41
+ elsif args.has_key?(attr_name.to_s)
42
+ self.send(:"#{attr_name}=", args[attr_name.to_s])
43
+ end
44
+ end
45
+ end
46
+
47
+ def participation_status=(val)
48
+ @participation_status = parse_participation_status(val)
49
+ end
50
+
51
+ def date=(val)
52
+ if val.is_a?(Integer)
53
+ @date = parse_date_in_seconds(val)
54
+ else
55
+ @date = val
56
+ end
57
+ end
58
+
59
+ def to_hash(options = {})
60
+ hash = ATTRS.inject({}) do |hash, attr_name|
61
+ hash[attr_name] = self.send(attr_name)
62
+ hash
63
+ end
64
+ hash.reject! { |key, value| options[:except].include?(key.to_sym) || options[:except].include?(key.to_s) } if options[:except]
65
+ hash.reject! { |key, value| !options[:only].include?(key.to_sym) && !options[:only].include?(key.to_s) } if options[:only]
66
+ hash
67
+ end
68
+
69
+ private
70
+
71
+ def parse_participation_status(status)
72
+ possible_values = {
73
+ 'NE' => :needs_action,
74
+ 'AC' => :accept,
75
+ 'TE' => :tentative,
76
+ 'DE' => :declined,
77
+ 'DG' => :delegated,
78
+ 'CO' => :completed,
79
+ 'IN' => :in_process,
80
+ 'WE' => :waiting,
81
+ 'DF' => :deferred
82
+ }
83
+ possible_values[status] || status
84
+ end
85
+
86
+ def parse_date_in_seconds(seconds)
87
+ Time.at(seconds / 1000)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,27 @@
1
+ module Zimbra
2
+ class Calendar < Folder
3
+ class << self
4
+ def all
5
+ CalendarService.find_all_by_view('appointment').reject { |c| c.view.nil? || c.view != 'appointment' }
6
+ end
7
+ end
8
+
9
+ def appointments
10
+ Zimbra::Appointment.find_all_by_calendar_id(id)
11
+ end
12
+ end
13
+
14
+ class CalendarService < FolderService
15
+ def parse_xml_responses(xml)
16
+ Parser.get_all_response(xml)
17
+ end
18
+
19
+ class Parser < Zimbra::FolderService::Parser
20
+ class << self
21
+ def initialize_from_attributes(folder_attributes)
22
+ Zimbra::Calendar.new(folder_attributes)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ # http://files.zimbra.com/docs/soap_api/8.0.4/soap-docs-804/api-reference/zimbraAdmin/DelegateAuth.html
2
+
3
+ module Zimbra
4
+ class DelegateAuthToken
5
+ class << self
6
+ def for_account_name(account_name)
7
+ DelegateAuthTokenService.get_by_account_name(account_name)
8
+ end
9
+ end
10
+
11
+ attr_accessor :account_name, :token, :lifetime
12
+
13
+ def initialize(args = {})
14
+ self.account_name = args[:account_name]
15
+ self.token = args[:token]
16
+ self.lifetime = args[:lifetime]
17
+ end
18
+ end
19
+
20
+ class DelegateAuthTokenService < HandsoapService
21
+ def get_by_account_name(account_name)
22
+ xml = invoke("n2:DelegateAuthRequest") do |message|
23
+ Builder.get_by_account_name(message, account_name)
24
+ end
25
+ return nil unless xml
26
+ Parser.delegate_auth_token_response(account_name, xml)
27
+ end
28
+
29
+ class Builder
30
+ class << self
31
+ def get_by_account_name(message, account_name)
32
+ message.add 'account', account_name do |c|
33
+ c.set_attr 'by', 'name'
34
+ end
35
+ end
36
+ end
37
+ end
38
+ class Parser
39
+ class << self
40
+ def delegate_auth_token_response(account_name, response)
41
+ auth_token = (response/'//n2:authToken').to_s
42
+ lifetime = (response/'//n2:lifetime').to_i
43
+
44
+ Zimbra::DelegateAuthToken.new(account_name: account_name, token: auth_token, lifetime: lifetime)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ module Zimbra
2
+ class Hash < ::Hash
3
+ # Thanks to https://gist.github.com/dimus/335286 for this code
4
+
5
+ class << self
6
+ def symbolize_keys(hash, recursive = false)
7
+ hash.keys.each do |key|
8
+ value = hash.delete(key)
9
+ key = key.respond_to?(:to_sym) ? key.to_sym : key
10
+ hash[key] = (recursive && value.is_a?(::Hash)) ? symbolize_keys(value.dup, recursive) : value
11
+ end
12
+ hash
13
+ end
14
+
15
+ def from_xml(xml_io)
16
+ begin
17
+ result = Nokogiri::XML(xml_io)
18
+ return { result.root.name.to_sym => xml_node_to_hash(result.root)}
19
+ rescue Exception => e
20
+ # raise your custom exception here
21
+ end
22
+ end
23
+
24
+ def xml_node_to_hash(node)
25
+ # If we are at the root of the document, start the hash
26
+ if node.element?
27
+ result_hash = {}
28
+ if node.attributes != {}
29
+ result_hash[:attributes] = {}
30
+ node.attributes.keys.each do |key|
31
+ result_hash[:attributes][node.attributes[key].name.to_sym] = prepare(node.attributes[key].value)
32
+ end
33
+ end
34
+ if node.children.size > 0
35
+ node.children.each do |child|
36
+ result = xml_node_to_hash(child)
37
+
38
+ if child.name == "text"
39
+ unless child.next_sibling || child.previous_sibling
40
+ if result_hash[:attributes]
41
+ result_hash['value'] = prepare(result)
42
+ return result_hash
43
+ else
44
+ return prepare(result)
45
+ end
46
+ end
47
+ elsif result_hash[child.name.to_sym]
48
+ if result_hash[child.name.to_sym].is_a?(Object::Array)
49
+ result_hash[child.name.to_sym] << prepare(result)
50
+ else
51
+ result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << prepare(result)
52
+ end
53
+ else
54
+ result_hash[child.name.to_sym] = prepare(result)
55
+ end
56
+ end
57
+
58
+ return result_hash
59
+ else
60
+ return result_hash
61
+ end
62
+ else
63
+ return prepare(node.content.to_s)
64
+ end
65
+ end
66
+
67
+ def prepare(data)
68
+ (data.class == String && data.to_i.to_s == data) ? data.to_i : data
69
+ end
70
+ end
71
+ end
72
+ end