sugarcrm_emp 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +29 -0
- data/Gemfile +14 -0
- data/LICENSE +20 -0
- data/README.rdoc +275 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/WATCHLIST.rdoc +7 -0
- data/bin/sugarcrm +26 -0
- data/lib/rails/generators/sugarcrm/config/config_generator.rb +22 -0
- data/lib/rails/generators/sugarcrm/config/templates/initializer.rb +4 -0
- data/lib/rails/generators/sugarcrm/config/templates/sugarcrm.yml +19 -0
- data/lib/sugarcrm/associations/association.rb +170 -0
- data/lib/sugarcrm/associations/association_cache.rb +36 -0
- data/lib/sugarcrm/associations/association_collection.rb +141 -0
- data/lib/sugarcrm/associations/association_methods.rb +91 -0
- data/lib/sugarcrm/associations/associations.rb +61 -0
- data/lib/sugarcrm/associations.rb +5 -0
- data/lib/sugarcrm/attributes/attribute_methods.rb +203 -0
- data/lib/sugarcrm/attributes/attribute_serializers.rb +55 -0
- data/lib/sugarcrm/attributes/attribute_typecast.rb +44 -0
- data/lib/sugarcrm/attributes/attribute_validations.rb +62 -0
- data/lib/sugarcrm/attributes.rb +4 -0
- data/lib/sugarcrm/base.rb +355 -0
- data/lib/sugarcrm/config/sugarcrm.yaml +10 -0
- data/lib/sugarcrm/connection/api/get_available_modules.rb +22 -0
- data/lib/sugarcrm/connection/api/get_document_revision.rb +14 -0
- data/lib/sugarcrm/connection/api/get_entries.rb +23 -0
- data/lib/sugarcrm/connection/api/get_entries_count.rb +20 -0
- data/lib/sugarcrm/connection/api/get_entry.rb +23 -0
- data/lib/sugarcrm/connection/api/get_entry_list.rb +31 -0
- data/lib/sugarcrm/connection/api/get_module_fields.rb +15 -0
- data/lib/sugarcrm/connection/api/get_note_attachment.rb +14 -0
- data/lib/sugarcrm/connection/api/get_relationships.rb +30 -0
- data/lib/sugarcrm/connection/api/get_report_entries.rb +17 -0
- data/lib/sugarcrm/connection/api/get_server_info.rb +7 -0
- data/lib/sugarcrm/connection/api/get_user_id.rb +13 -0
- data/lib/sugarcrm/connection/api/get_user_team_id.rb +14 -0
- data/lib/sugarcrm/connection/api/login.rb +18 -0
- data/lib/sugarcrm/connection/api/logout.rb +15 -0
- data/lib/sugarcrm/connection/api/seamless_login.rb +13 -0
- data/lib/sugarcrm/connection/api/search_by_module.rb +25 -0
- data/lib/sugarcrm/connection/api/set_campaign_merge.rb +15 -0
- data/lib/sugarcrm/connection/api/set_document_revision.rb +35 -0
- data/lib/sugarcrm/connection/api/set_entries.rb +15 -0
- data/lib/sugarcrm/connection/api/set_entry.rb +15 -0
- data/lib/sugarcrm/connection/api/set_note_attachment.rb +25 -0
- data/lib/sugarcrm/connection/api/set_relationship.rb +27 -0
- data/lib/sugarcrm/connection/api/set_relationships.rb +22 -0
- data/lib/sugarcrm/connection/connection.rb +201 -0
- data/lib/sugarcrm/connection/helper.rb +50 -0
- data/lib/sugarcrm/connection/request.rb +61 -0
- data/lib/sugarcrm/connection/response.rb +91 -0
- data/lib/sugarcrm/connection.rb +5 -0
- data/lib/sugarcrm/connection_pool.rb +163 -0
- data/lib/sugarcrm/exceptions.rb +23 -0
- data/lib/sugarcrm/extensions/README.txt +23 -0
- data/lib/sugarcrm/finders/dynamic_finder_match.rb +41 -0
- data/lib/sugarcrm/finders/finder_methods.rb +243 -0
- data/lib/sugarcrm/finders.rb +2 -0
- data/lib/sugarcrm/module.rb +174 -0
- data/lib/sugarcrm/module_methods.rb +91 -0
- data/lib/sugarcrm/session.rb +218 -0
- data/lib/sugarcrm.rb +22 -0
- data/sugarcrm.gemspec +178 -0
- data/test/config_test.yaml +15 -0
- data/test/connection/test_get_available_modules.rb +9 -0
- data/test/connection/test_get_entries.rb +15 -0
- data/test/connection/test_get_entry.rb +22 -0
- data/test/connection/test_get_entry_list.rb +23 -0
- data/test/connection/test_get_module_fields.rb +11 -0
- data/test/connection/test_get_relationships.rb +12 -0
- data/test/connection/test_get_server_info.rb +9 -0
- data/test/connection/test_get_user_id.rb +9 -0
- data/test/connection/test_get_user_team_id.rb +9 -0
- data/test/connection/test_login.rb +9 -0
- data/test/connection/test_logout.rb +9 -0
- data/test/connection/test_set_document_revision.rb +28 -0
- data/test/connection/test_set_entry.rb +15 -0
- data/test/connection/test_set_note_attachment.rb +16 -0
- data/test/connection/test_set_relationship.rb +18 -0
- data/test/extensions_test/patch.rb +9 -0
- data/test/helper.rb +17 -0
- data/test/test_association_collection.rb +11 -0
- data/test/test_associations.rb +156 -0
- data/test/test_connection.rb +13 -0
- data/test/test_connection_pool.rb +40 -0
- data/test/test_finders.rb +201 -0
- data/test/test_module.rb +51 -0
- data/test/test_request.rb +35 -0
- data/test/test_response.rb +26 -0
- data/test/test_session.rb +136 -0
- data/test/test_sugarcrm.rb +213 -0
- metadata +266 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Returns the ID, module name and fields for specified modules.
|
3
|
+
# Supported modules are Accounts, Bugs, Calls, Cases, Contacts,
|
4
|
+
# Leads, Opportunities, Projects, Project Tasks, and Quotes.
|
5
|
+
def search_by_module(search_string, modules, opts={})
|
6
|
+
login! unless logged_in?
|
7
|
+
|
8
|
+
options = {
|
9
|
+
:offset => nil,
|
10
|
+
:limit => nil,
|
11
|
+
}.merge! opts
|
12
|
+
|
13
|
+
json = <<-EOF
|
14
|
+
{
|
15
|
+
"session": "#{@sugar_session_id}",
|
16
|
+
"search_string": "#{search_string}",
|
17
|
+
"modules": "#{modules}",
|
18
|
+
"offset": #{options[:offset]},
|
19
|
+
"max_results": #{options[:limit]}
|
20
|
+
}
|
21
|
+
EOF
|
22
|
+
json.gsub!(/^\s{6}/,'')
|
23
|
+
send!(:search_by_module, json)
|
24
|
+
end
|
25
|
+
end; end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Performs a mail merge for the specified campaign.
|
3
|
+
def set_campaign_merge(targets, campaign_id)
|
4
|
+
login! unless logged_in?
|
5
|
+
json = <<-EOF
|
6
|
+
{
|
7
|
+
"session": "#{@sugar_session_id}",
|
8
|
+
"targets": #{targets.to_json},
|
9
|
+
"campaign-id": "#{campaign_id}"
|
10
|
+
}
|
11
|
+
EOF
|
12
|
+
json.gsub!(/^\s{6}/,'')
|
13
|
+
send!(:set_campaign_merge, json)
|
14
|
+
end
|
15
|
+
end; end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Sets a new revision for a document.
|
3
|
+
def set_document_revision(document_id, revision_number, opts={})
|
4
|
+
options = {
|
5
|
+
:file => '',
|
6
|
+
:file_name => '',
|
7
|
+
:document_name => nil
|
8
|
+
}.merge! opts
|
9
|
+
|
10
|
+
# Raise an exception of we try to pass :file, but not :file_name
|
11
|
+
if (!options[:file].empty? && options[:file_name].empty?)
|
12
|
+
raise ArgumentException, ":file_name must be specified if :file is specified"
|
13
|
+
end
|
14
|
+
|
15
|
+
# If no document_name is given, use the file_name
|
16
|
+
options[:document_name] ||= options[:file_name]
|
17
|
+
|
18
|
+
login! unless logged_in?
|
19
|
+
|
20
|
+
json = <<-EOF
|
21
|
+
{
|
22
|
+
"session": "#{@sugar_session_id}",
|
23
|
+
"document_revision": {
|
24
|
+
"id": "#{document_id}",
|
25
|
+
"document_name": "#{options[:document_name]}",
|
26
|
+
"revision": "#{revision_number}",
|
27
|
+
"filename": "#{options[:file_name]}",
|
28
|
+
"file": "#{b64_encode(options[:file])}"
|
29
|
+
}
|
30
|
+
}
|
31
|
+
EOF
|
32
|
+
json.gsub!(/^\s{6}/,'')
|
33
|
+
send!(:set_document_revision, json)
|
34
|
+
end
|
35
|
+
end; end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Creates or updates a list of SugarBeans.
|
3
|
+
def set_entries(module_name, name_value_lists)
|
4
|
+
login! unless logged_in?
|
5
|
+
json = <<-EOF
|
6
|
+
{
|
7
|
+
"session": "#{@sugar_session_id}",
|
8
|
+
"module_name": "#{module_name}",
|
9
|
+
"name_value_list": #{name_value_lists.to_json}
|
10
|
+
}
|
11
|
+
EOF
|
12
|
+
json.gsub!(/^\s{6}/,'')
|
13
|
+
send!(:set_entries, json)
|
14
|
+
end
|
15
|
+
end; end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Creates or updates a single SugarBean.
|
3
|
+
def set_entry(module_name, name_value_list)
|
4
|
+
login! unless logged_in?
|
5
|
+
json = <<-EOF
|
6
|
+
{
|
7
|
+
"session": "#{@sugar_session_id}",
|
8
|
+
"module_name": "#{module_name}",
|
9
|
+
"name_value_list": #{name_value_list.to_json}
|
10
|
+
}
|
11
|
+
EOF
|
12
|
+
json.gsub!(/^\s{6}/,'')
|
13
|
+
send!(:set_entry, json)
|
14
|
+
end
|
15
|
+
end; end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Creates or updates an attachment on a note
|
3
|
+
def set_note_attachment(id, filename, file, opts={})
|
4
|
+
options = {
|
5
|
+
:module_id => '',
|
6
|
+
:module_name => ''
|
7
|
+
}.merge! opts
|
8
|
+
|
9
|
+
login! unless logged_in?
|
10
|
+
json = <<-EOF
|
11
|
+
{
|
12
|
+
"session": "#{@sugar_session_id}",
|
13
|
+
"note": {
|
14
|
+
"id": "#{id}",
|
15
|
+
"filename": "#{filename}",
|
16
|
+
"file": "#{b64_encode(file)}",
|
17
|
+
"related_module_id": "#{options[:module_id]}",
|
18
|
+
"related_module_name": "#{options[:module_name]}"
|
19
|
+
}
|
20
|
+
}
|
21
|
+
EOF
|
22
|
+
json.gsub!(/^\s{6}/,'')
|
23
|
+
send!(:set_note_attachment, json)
|
24
|
+
end
|
25
|
+
end; end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Sets a single relationship between two SugarBeans.
|
3
|
+
def set_relationship(module_name, module_id, link_field_name, related_ids, opts={})
|
4
|
+
login! unless logged_in?
|
5
|
+
options = {
|
6
|
+
:name_value_list => [],
|
7
|
+
:delete => 0,
|
8
|
+
}.merge! opts
|
9
|
+
raise ArgumentError, "related_ids must be an Array" unless related_ids.class == Array
|
10
|
+
json = <<-EOF
|
11
|
+
{
|
12
|
+
"session": "#{@sugar_session_id}",
|
13
|
+
"module_name": "#{module_name}",
|
14
|
+
"module_id": "#{module_id}",
|
15
|
+
"link_field_name": "#{link_field_name}",
|
16
|
+
"related_ids": #{related_ids.to_json},
|
17
|
+
"name_value_list": #{options[:name_value_list].to_json},
|
18
|
+
"delete": #{options[:delete]}
|
19
|
+
}
|
20
|
+
EOF
|
21
|
+
json.gsub!(/^\s{6}/,'')
|
22
|
+
# TODO: Add a handler for the response. By default it returns a hash like:
|
23
|
+
# {failed => 0, deleted => 0, created => 1}. We should add this to the
|
24
|
+
# Response.handle method and return true/false depending on the outcome.
|
25
|
+
send!(:set_relationship, json)
|
26
|
+
end
|
27
|
+
end; end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Sets multiple relationships between two SugarBeans.
|
3
|
+
def set_relationships(module_names, module_ids, link_field_names, related_ids)
|
4
|
+
login! unless logged_in?
|
5
|
+
|
6
|
+
[module_names, module_ids, link_field_names, related_ids].each do |arg|
|
7
|
+
raise ArgumentError, "argument must be an Array" unless arg.class == Array
|
8
|
+
end
|
9
|
+
|
10
|
+
json = <<-EOF
|
11
|
+
{
|
12
|
+
"session": "#{@sugar_session_id}",
|
13
|
+
"module_names": "#{module_names.to_json}",
|
14
|
+
"module_ids": #{module_ids.to_json},
|
15
|
+
"link_field_names": #{link_field_names.to_json},
|
16
|
+
"related_ids": #{related_ids.to_json}
|
17
|
+
}
|
18
|
+
EOF
|
19
|
+
json.gsub!(/^\s{6}/,'')
|
20
|
+
send!(:set_relationships, json)
|
21
|
+
end
|
22
|
+
end; end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
|
3
|
+
URL = "/service/v2/rest.php"
|
4
|
+
# Set this to filter out debug output on a certain method (i.e. get_modules, or get_fields)
|
5
|
+
DONT_SHOW_DEBUG_FOR = []
|
6
|
+
RESPONSE_IS_NOT_JSON = [:get_user_id, :get_user_team_id]
|
7
|
+
|
8
|
+
attr :url, true
|
9
|
+
attr :user, false
|
10
|
+
attr :pass, false
|
11
|
+
attr :session, true
|
12
|
+
attr :sugar_session_id, true
|
13
|
+
attr :connection, true
|
14
|
+
attr :options, true
|
15
|
+
attr :request, true
|
16
|
+
attr :response, true
|
17
|
+
attr :errors, true
|
18
|
+
|
19
|
+
# This is the singleton connection class.
|
20
|
+
def initialize(url, user, pass, options={})
|
21
|
+
@options = {
|
22
|
+
:debug => false,
|
23
|
+
:register_modules => true,
|
24
|
+
:load_environment => true
|
25
|
+
}.merge(options)
|
26
|
+
@errors = []
|
27
|
+
@url = URI.parse(url)
|
28
|
+
@user = user
|
29
|
+
@pass = pass
|
30
|
+
@request = ""
|
31
|
+
@response = ""
|
32
|
+
resolve_url
|
33
|
+
login!
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check to see if we are logged in
|
38
|
+
def logged_in?
|
39
|
+
connect! unless connected?
|
40
|
+
@sugar_session_id ? true : false
|
41
|
+
end
|
42
|
+
|
43
|
+
# Login
|
44
|
+
def login!
|
45
|
+
@sugar_session_id = login["id"]
|
46
|
+
raise SugarCRM::LoginError, "Invalid Login" unless logged_in?
|
47
|
+
end
|
48
|
+
|
49
|
+
def logout
|
50
|
+
logout
|
51
|
+
@sugar_session_id = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
# Check to see if we are connected
|
55
|
+
def connected?
|
56
|
+
return false unless @connection
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Connect
|
61
|
+
def connect!
|
62
|
+
@connection = HTTPClient.new
|
63
|
+
end
|
64
|
+
alias :reconnect! :connect!
|
65
|
+
|
66
|
+
# Send a request to the Sugar Instance
|
67
|
+
def send!(method, json, max_retry=3)
|
68
|
+
if max_retry == 0
|
69
|
+
raise SugarCRM::RetryLimitExceeded, "SugarCRM::Connection Errors: \n#{@errors.reverse.join "\n\s\s"}"
|
70
|
+
end
|
71
|
+
@request = SugarCRM::Request.new(@url, method, json, @options[:debug])
|
72
|
+
# Send Ze Reques
|
73
|
+
begin
|
74
|
+
if @request.length > 3900
|
75
|
+
@response = @connection.post(@url, @request)
|
76
|
+
else
|
77
|
+
@response = @connection.get(@url, @request)
|
78
|
+
end
|
79
|
+
return handle_response
|
80
|
+
# Timeouts are usually a server side issue
|
81
|
+
rescue Timeout::Error => error
|
82
|
+
@errors << error
|
83
|
+
send!(method, json, max_retry.pred)
|
84
|
+
# Lower level errors requiring a reconnect
|
85
|
+
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, EOFError => error
|
86
|
+
@errors << error
|
87
|
+
reconnect!
|
88
|
+
send!(method, json, max_retry.pred)
|
89
|
+
# Handle invalid sessions
|
90
|
+
rescue SugarCRM::InvalidSession => error
|
91
|
+
@errors << error
|
92
|
+
old_session = @sugar_session_id.dup
|
93
|
+
login!
|
94
|
+
# Update the session id in the request that we want to retry.
|
95
|
+
json.gsub!(old_session, @sugar_session_id)
|
96
|
+
send!(method, json, max_retry.pred)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
alias :retry! :send!
|
100
|
+
|
101
|
+
def debug=(debug)
|
102
|
+
options[:debug] = debug
|
103
|
+
end
|
104
|
+
|
105
|
+
def debug?
|
106
|
+
options[:debug]
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def handle_response
|
112
|
+
case @response.status
|
113
|
+
when 200
|
114
|
+
return process_response
|
115
|
+
when 404
|
116
|
+
raise SugarCRM::InvalidSugarCRMUrl, "#{@url} is invalid"
|
117
|
+
when 500
|
118
|
+
raise SugarCRM::InvalidRequest, "#{@request} is invalid"
|
119
|
+
else
|
120
|
+
if @options[:debug]
|
121
|
+
puts "#{@request.method}: Raw Response:"
|
122
|
+
puts @response.body
|
123
|
+
puts "\n"
|
124
|
+
end
|
125
|
+
raise SugarCRM::UnhandledResponse, "Can't handle response #{@response}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def process_response
|
130
|
+
empty_body?
|
131
|
+
if response_contains_json?
|
132
|
+
return parse_response
|
133
|
+
else
|
134
|
+
return @response.body
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def resolve_url
|
139
|
+
# Appends the rest.php path onto the end of the URL if it's not included
|
140
|
+
if @url.path !~ /rest.php$/
|
141
|
+
@url.path += URL
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Complain if our body is empty.
|
146
|
+
def empty_body?
|
147
|
+
raise SugarCRM::EmptyResponse unless @response.body
|
148
|
+
end
|
149
|
+
|
150
|
+
# Some methods are dumb and don't return a JSON Response
|
151
|
+
def response_contains_json?
|
152
|
+
if RESPONSE_IS_NOT_JSON.include? @request.method
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
true
|
156
|
+
end
|
157
|
+
|
158
|
+
def parse_response
|
159
|
+
begin
|
160
|
+
# Push it through the old meat grinder.
|
161
|
+
json = JSON.parse(@response.body)
|
162
|
+
rescue StandardError => e
|
163
|
+
# Complain if we can't parse
|
164
|
+
raise UnhandledResponse, @response.body
|
165
|
+
end
|
166
|
+
# Do ze debugs!
|
167
|
+
nice_debugging_for json
|
168
|
+
# Check for an invalid session
|
169
|
+
invalid_session? json
|
170
|
+
# Check for an empty result set
|
171
|
+
if zero_results? json
|
172
|
+
return nil
|
173
|
+
end
|
174
|
+
json
|
175
|
+
end
|
176
|
+
|
177
|
+
# Check if we got an invalid session error back
|
178
|
+
# something like:
|
179
|
+
# {"name"=>"Invalid Session ID",
|
180
|
+
# "number"=>11,
|
181
|
+
# "description"=>"The session ID is invalid"}
|
182
|
+
def invalid_session?(json)
|
183
|
+
return false unless json["name"]
|
184
|
+
return false if @request.method == :logout
|
185
|
+
raise SugarCRM::InvalidSession if json["name"] == "Invalid Session ID"
|
186
|
+
end
|
187
|
+
|
188
|
+
def zero_results?(json)
|
189
|
+
json["result_count"] == 0
|
190
|
+
end
|
191
|
+
|
192
|
+
# Filter debugging on REALLY BIG responses
|
193
|
+
def nice_debugging_for(json)
|
194
|
+
if @options[:debug] && !(DONT_SHOW_DEBUG_FOR.include? @request.method)
|
195
|
+
puts "#{@request.method}: JSON Response:"
|
196
|
+
pp json
|
197
|
+
puts "\n"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end; end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Attempts to return a list of fields for the target of the association.
|
3
|
+
# i.e. if we are associating Contact -> Account, using the "contacts" link
|
4
|
+
# field name - this will lookup the contacts association and try to determine
|
5
|
+
# the target object type (Contact). It will then pull the fields for that object
|
6
|
+
# and shove them in the related_fields portion of the get_relationship request.
|
7
|
+
def resolve_related_fields(module_name, link_field)
|
8
|
+
a = Association.new(class_for(module_name), link_field)
|
9
|
+
if a.target
|
10
|
+
fields = a.target.new.attributes.keys
|
11
|
+
else
|
12
|
+
fields = ["id"]
|
13
|
+
end
|
14
|
+
fields.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
def resolve_fields(module_name, fields)
|
18
|
+
# FIXME: This is to work around a bug in SugarCRM 6.0
|
19
|
+
# where no fields are returned if no fields are specified
|
20
|
+
if fields.length == 0
|
21
|
+
mod = Module.find(module_name.classify, @session)
|
22
|
+
if mod
|
23
|
+
fields = mod.fields.keys
|
24
|
+
else
|
25
|
+
fields = ["id"]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
return fields.to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an instance of class for the provided module name
|
32
|
+
def class_for(module_name)
|
33
|
+
begin
|
34
|
+
class_const = @session.namespace_const.const_get(module_name.classify)
|
35
|
+
klass = class_const.new
|
36
|
+
rescue NameError
|
37
|
+
raise InvalidModule, "Module: #{module_name} is not registered"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# We need to strip newlines from Base64 encoding for JSON validation purposes.
|
42
|
+
def b64_encode(file)
|
43
|
+
Base64.encode64(file).gsub(/\n/, '')
|
44
|
+
end
|
45
|
+
|
46
|
+
def b64_decode(file)
|
47
|
+
Base64.decode64(file)
|
48
|
+
end
|
49
|
+
|
50
|
+
end; end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module SugarCRM; class Request
|
2
|
+
attr :request, true
|
3
|
+
attr :url, true
|
4
|
+
attr :method, true
|
5
|
+
attr :json, true
|
6
|
+
attr :http_method
|
7
|
+
|
8
|
+
def initialize(url, method, json, debug=false)
|
9
|
+
@url = url
|
10
|
+
@method = method
|
11
|
+
@json = escape(json)
|
12
|
+
@request = 'method=' << @method.to_s
|
13
|
+
@request << '&input_type=JSON'
|
14
|
+
@request << '&response_type=JSON'
|
15
|
+
@request << '&rest_data=' << @json
|
16
|
+
if debug
|
17
|
+
puts "#{method}: Request:"
|
18
|
+
puts json
|
19
|
+
puts "\n"
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def escape(json)
|
25
|
+
# BUG: SugarCRM doesn't properly handle '"' inside of JSON for some reason. Let's unescape any html elements.
|
26
|
+
j = convert_reserved_characters(json)
|
27
|
+
# Now we escape the resulting string.
|
28
|
+
j = CGI.escape(j)
|
29
|
+
j
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO: Fix this so that it JSON.parse will consume it.
|
33
|
+
def unescape
|
34
|
+
j = CGI.unescape(@json)
|
35
|
+
j.gsub!(/\n/, '')
|
36
|
+
end
|
37
|
+
|
38
|
+
def bytesize
|
39
|
+
self.to_s.bytesize
|
40
|
+
end
|
41
|
+
|
42
|
+
def length
|
43
|
+
self.to_s.length
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
@request
|
48
|
+
end
|
49
|
+
alias :to_str :to_s
|
50
|
+
|
51
|
+
# A tiny helper for converting reserved characters for html encoding
|
52
|
+
def convert_reserved_characters(string)
|
53
|
+
string.gsub!(/"/, '\"')
|
54
|
+
string.gsub!(/'/, '\'')
|
55
|
+
string.gsub!(/&/, '\&')
|
56
|
+
string.gsub!(/</, '\<')
|
57
|
+
string.gsub!(/</, '\>')
|
58
|
+
string
|
59
|
+
end
|
60
|
+
|
61
|
+
end; end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module SugarCRM; class Response
|
2
|
+
class << self
|
3
|
+
# This class handles the response from the server.
|
4
|
+
# It tries to convert the response into an object such as User
|
5
|
+
# or an object collection. If it fails, it just returns the response hash
|
6
|
+
def handle(json, session)
|
7
|
+
r = new(json, session)
|
8
|
+
begin
|
9
|
+
return r.to_obj
|
10
|
+
rescue UninitializedModule => e
|
11
|
+
raise e
|
12
|
+
rescue InvalidAttribute => e
|
13
|
+
raise e
|
14
|
+
rescue InvalidAttributeType => e
|
15
|
+
raise e
|
16
|
+
rescue => e
|
17
|
+
if session.connection.debug?
|
18
|
+
puts "Failed to process JSON:"
|
19
|
+
pp json
|
20
|
+
end
|
21
|
+
raise e
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr :response, false
|
27
|
+
|
28
|
+
def initialize(json, session, opts={})
|
29
|
+
@options = { :always_return_array => false }.merge! opts
|
30
|
+
@response = json
|
31
|
+
@response = json.with_indifferent_access if json.is_a? Hash
|
32
|
+
@session = session
|
33
|
+
end
|
34
|
+
|
35
|
+
# Tries to instantiate and return an object with the values
|
36
|
+
# populated from the response
|
37
|
+
def to_obj
|
38
|
+
# If this is not a "entry_list" response, just return
|
39
|
+
return @response unless @response && @response["entry_list"]
|
40
|
+
|
41
|
+
objects = []
|
42
|
+
@response["entry_list"].each do |object|
|
43
|
+
attributes = []
|
44
|
+
_module = resolve_module(object)
|
45
|
+
attributes = flatten_name_value_list(object)
|
46
|
+
namespace = @session.namespace_const
|
47
|
+
if namespace.const_get(_module)
|
48
|
+
if attributes.length == 0
|
49
|
+
raise AttributeParsingError, "response contains objects without attributes!"
|
50
|
+
end
|
51
|
+
objects << namespace.const_get(_module).new(attributes)
|
52
|
+
else
|
53
|
+
raise InvalidModule, "#{_module} does not exist, or is not accessible"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
# If we only have one result, just return the object
|
57
|
+
if objects.length == 1 && !@options[:always_return_array]
|
58
|
+
return objects[0]
|
59
|
+
else
|
60
|
+
return objects
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_json
|
65
|
+
@response.to_json
|
66
|
+
end
|
67
|
+
|
68
|
+
def resolve_module(list)
|
69
|
+
list["module_name"].classify
|
70
|
+
end
|
71
|
+
|
72
|
+
def flatten_name_value_list(list)
|
73
|
+
if list["name_value_list"]
|
74
|
+
return flatten(list["name_value_list"])
|
75
|
+
else
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Takes a hash like { "first_name" => {"name" => "first_name", "value" => "John"}}
|
81
|
+
# And flattens it into {"first_name" => "John"}
|
82
|
+
def flatten(list)
|
83
|
+
raise ArgumentError, list[0]['value'] if list[0] && list[0]['name'] == 'warning'
|
84
|
+
raise ArgumentError, 'method parameter must respond to #each_pair' unless list.respond_to? :each_pair
|
85
|
+
flat_list = {}
|
86
|
+
list.each_pair do |k,v|
|
87
|
+
flat_list[k.to_sym] = v["value"]
|
88
|
+
end
|
89
|
+
flat_list
|
90
|
+
end
|
91
|
+
end; end
|