zimbra 0.0.4 → 0.0.5

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 (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