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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.irbrc +6 -0
- data/Gemfile +6 -0
- data/README +21 -0
- data/Rakefile +1 -0
- data/lib/zimbra.rb +94 -0
- data/lib/zimbra/account.rb +94 -0
- data/lib/zimbra/acl.rb +63 -0
- data/lib/zimbra/alias.rb +4 -0
- data/lib/zimbra/appointment.rb +274 -0
- data/lib/zimbra/appointment/alarm.rb +101 -0
- data/lib/zimbra/appointment/attendee.rb +97 -0
- data/lib/zimbra/appointment/invite.rb +360 -0
- data/lib/zimbra/appointment/recur_exception.rb +83 -0
- data/lib/zimbra/appointment/recur_rule.rb +184 -0
- data/lib/zimbra/appointment/reply.rb +91 -0
- data/lib/zimbra/auth.rb +46 -0
- data/lib/zimbra/base.rb +217 -0
- data/lib/zimbra/calendar.rb +27 -0
- data/lib/zimbra/common_elements.rb +51 -0
- data/lib/zimbra/cos.rb +124 -0
- data/lib/zimbra/delegate_auth_token.rb +49 -0
- data/lib/zimbra/directory.rb +175 -0
- data/lib/zimbra/distribution_list.rb +147 -0
- data/lib/zimbra/domain.rb +63 -0
- data/lib/zimbra/ext/handsoap_curb_driver.rb +21 -0
- data/lib/zimbra/ext/hash.rb +72 -0
- data/lib/zimbra/ext/string.rb +10 -0
- data/lib/zimbra/extra/date_helpers.rb +111 -0
- data/lib/zimbra/folder.rb +100 -0
- data/lib/zimbra/handsoap_account_service.rb +44 -0
- data/lib/zimbra/handsoap_service.rb +75 -0
- data/lib/zimbra/version.rb +3 -0
- data/spec/fixtures/xml_api_requests/appointments/create.xml +84 -0
- data/spec/fixtures/xml_api_responses/alarms/15_minutes_before.xml +26 -0
- data/spec/fixtures/xml_api_responses/alarms/using_all_intervals.xml +26 -0
- data/spec/fixtures/xml_api_responses/appointments/appointment_response_1.xml +40 -0
- data/spec/fixtures/xml_api_responses/attendees/one_attendee_and_one_reply.xml +27 -0
- data/spec/fixtures/xml_api_responses/attendees/three_attendees_response_1.xml +30 -0
- data/spec/fixtures/xml_api_responses/multiple_invites/recurring_with_exceptions.xml +109 -0
- data/spec/fixtures/xml_api_responses/recur_rules/day_27_of_every_2_months.xml +27 -0
- data/spec/fixtures/xml_api_responses/recur_rules/every_2_days.xml +26 -0
- data/spec/fixtures/xml_api_responses/recur_rules/every_3_weeks_on_tuesday_and_friday.xml +30 -0
- data/spec/fixtures/xml_api_responses/recur_rules/every_day_50_instances.xml +27 -0
- data/spec/fixtures/xml_api_responses/recur_rules/every_monday_wednesday_friday.xml +31 -0
- data/spec/fixtures/xml_api_responses/recur_rules/every_tuesday.xml +29 -0
- data/spec/fixtures/xml_api_responses/recur_rules/every_weekday_with_end_date.xml +34 -0
- data/spec/fixtures/xml_api_responses/recur_rules/every_year_on_february_2.xml +28 -0
- data/spec/fixtures/xml_api_responses/recur_rules/first_day_of_every_month.xml +36 -0
- data/spec/fixtures/xml_api_responses/recur_rules/first_monday_of_every_february.xml +31 -0
- data/spec/fixtures/xml_api_responses/recur_rules/first_weekend_day_of_every_month.xml +31 -0
- data/spec/fixtures/xml_api_responses/recur_rules/last_day_of_every_month.xml +36 -0
- data/spec/fixtures/xml_api_responses/recur_rules/second_day_of_every_2_months.xml +36 -0
- data/spec/fixtures/xml_api_responses/recur_rules/second_wednesday_of_every_month.xml +29 -0
- data/spec/fixtures/xml_api_responses/recur_rules/weekly_with_an_exception.xml +44 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/zimbra/acl_spec.rb +11 -0
- data/spec/zimbra/appointment/alarm_spec.rb +33 -0
- data/spec/zimbra/appointment/invite_spec.rb +62 -0
- data/spec/zimbra/appointment/recur_rule_spec.rb +307 -0
- data/spec/zimbra/appointment_spec.rb +175 -0
- data/spec/zimbra/common_elements_spec.rb +33 -0
- data/spec/zimbra/distribution_list_spec.rb +54 -0
- data/zimbra.gemspec +28 -0
- metadata +197 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.irbrc
ADDED
data/Gemfile
ADDED
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"]
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/zimbra.rb
ADDED
@@ -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
|
data/lib/zimbra/acl.rb
ADDED
@@ -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
|
data/lib/zimbra/alias.rb
ADDED
@@ -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
|